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 <elliott.codes@gmail.com>
This commit is contained in:
parent
9bac64a9c1
commit
78a8a58ee2
6 changed files with 141 additions and 117 deletions
1
assets/icons/code.svg
Normal file
1
assets/icons/code.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-code-xml"><path d="m18 16 4-4-4-4"/><path d="m6 8-4 4 4 4"/><path d="m14.5 4-5 16"/></svg>
|
After Width: | Height: | Size: 293 B |
13
assets/icons/spinner.svg
Normal file
13
assets/icons/spinner.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1803_28)">
|
||||||
|
<path d="M0.5 2C0.5 1.17157 1.17157 0.5 2 0.5V0.5C2.82843 0.5 3.5 1.17157 3.5 2V2C3.5 2.82843 2.82843 3.5 2 3.5V3.5C1.17157 3.5 0.5 2.82843 0.5 2V2Z" fill="black" fill-opacity="0.3"/>
|
||||||
|
<path d="M7.5 6C7.5 6.82843 6.82843 7.5 6 7.5V7.5C5.17157 7.5 4.5 6.82843 4.5 6V6C4.5 5.17157 5.17157 4.5 6 4.5V4.5C6.82843 4.5 7.5 5.17157 7.5 6V6Z" fill="black" fill-opacity="0.6"/>
|
||||||
|
<path d="M2 7.5C1.17157 7.5 0.5 6.82843 0.5 6V6C0.5 5.17157 1.17157 4.5 2 4.5V4.5C2.82843 4.5 3.5 5.17157 3.5 6V6C3.5 6.82843 2.82843 7.5 2 7.5V7.5Z" fill="black" fill-opacity="0.8"/>
|
||||||
|
<path d="M6 0.5C6.82843 0.5 7.5 1.17157 7.5 2V2C7.5 2.82843 6.82843 3.5 6 3.5V3.5C5.17157 3.5 4.5 2.82843 4.5 2V2C4.5 1.17157 5.17157 0.5 6 0.5V0.5Z" fill="black"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1803_28">
|
||||||
|
<rect width="8" height="8" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 956 B |
|
@ -269,7 +269,7 @@ impl AssistantChat {
|
||||||
composer_editor: cx.new_view(|cx| {
|
composer_editor: cx.new_view(|cx| {
|
||||||
let mut editor = Editor::auto_height(80, cx);
|
let mut editor = Editor::auto_height(80, cx);
|
||||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, 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
|
editor
|
||||||
}),
|
}),
|
||||||
list_state,
|
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<Self>) {
|
fn debug_project_index(&mut self, _: &DebugProjectIndex, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(index) = &self.project_index {
|
if let Some(index) = &self.project_index {
|
||||||
index.update(cx, |project_index, cx| {
|
index.update(cx, |project_index, cx| {
|
||||||
|
@ -594,7 +590,6 @@ impl AssistantChat {
|
||||||
element.child(Composer::new(
|
element.child(Composer::new(
|
||||||
body.clone(),
|
body.clone(),
|
||||||
self.user_store.read(cx).current_user(),
|
self.user_store.read(cx).current_user(),
|
||||||
true,
|
|
||||||
self.tool_registry.clone(),
|
self.tool_registry.clone(),
|
||||||
crate::ui::ModelSelector::new(
|
crate::ui::ModelSelector::new(
|
||||||
cx.view().downgrade(),
|
cx.view().downgrade(),
|
||||||
|
@ -773,7 +768,6 @@ impl Render for AssistantChat {
|
||||||
.child(Composer::new(
|
.child(Composer::new(
|
||||||
self.composer_editor.clone(),
|
self.composer_editor.clone(),
|
||||||
self.user_store.read(cx).current_user(),
|
self.user_store.read(cx).current_user(),
|
||||||
self.can_submit(),
|
|
||||||
self.tool_registry.clone(),
|
self.tool_registry.clone(),
|
||||||
crate::ui::ModelSelector::new(cx.view().downgrade(), self.model.clone())
|
crate::ui::ModelSelector::new(cx.view().downgrade(), self.model.clone())
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assistant_tooling::LanguageModelTool;
|
use assistant_tooling::LanguageModelTool;
|
||||||
use gpui::{prelude::*, AnyView, Model, Task};
|
use gpui::{percentage, prelude::*, Animation, AnimationExt, AnyView, Model, Task, Transformation};
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use semantic_index::{ProjectIndex, Status};
|
use semantic_index::{ProjectIndex, Status};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time::Duration};
|
||||||
use ui::{
|
use ui::{
|
||||||
div, prelude::*, CollapsibleContainer, Color, Icon, IconName, Label, SharedString,
|
div, prelude::*, ButtonLike, CollapsibleContainer, Color, Icon, IconName, Indicator, Label,
|
||||||
WindowContext,
|
SharedString, Tooltip, WindowContext,
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
|
||||||
|
@ -255,12 +255,63 @@ impl Render for ProjectIndexStatusView {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let status = self.project_index.read(cx).status();
|
let status = self.project_index.read(cx).status();
|
||||||
|
|
||||||
h_flex().gap_2().map(|element| match status {
|
let is_enabled = match status {
|
||||||
Status::Idle => element.child(Label::new("Project index ready")),
|
Status::Idle => true,
|
||||||
Status::Loading => element.child(Label::new("Project index loading...")),
|
_ => false,
|
||||||
Status::Scanning { remaining_count } => element.child(Label::new(format!(
|
};
|
||||||
"Project index scanning: {remaining_count} remaining..."
|
|
||||||
))),
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,12 @@ use std::sync::Arc;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{popover_menu, prelude::*, Avatar, ButtonLike, ContextMenu, Tooltip};
|
use ui::{popover_menu, prelude::*, Avatar, ButtonLike, ContextMenu, Tooltip};
|
||||||
|
|
||||||
use crate::{AssistantChat, CompletionProvider, Submit, SubmitMode};
|
use crate::{AssistantChat, CompletionProvider};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Composer {
|
pub struct Composer {
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
player: Option<Arc<User>>,
|
player: Option<Arc<User>>,
|
||||||
can_submit: bool,
|
|
||||||
tool_registry: Arc<ToolRegistry>,
|
tool_registry: Arc<ToolRegistry>,
|
||||||
model_selector: AnyElement,
|
model_selector: AnyElement,
|
||||||
}
|
}
|
||||||
|
@ -22,14 +21,12 @@ impl Composer {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
editor: View<Editor>,
|
editor: View<Editor>,
|
||||||
player: Option<Arc<User>>,
|
player: Option<Arc<User>>,
|
||||||
can_submit: bool,
|
|
||||||
tool_registry: Arc<ToolRegistry>,
|
tool_registry: Arc<ToolRegistry>,
|
||||||
model_selector: AnyElement,
|
model_selector: AnyElement,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
editor,
|
editor,
|
||||||
player,
|
player,
|
||||||
can_submit,
|
|
||||||
tool_registry,
|
tool_registry,
|
||||||
model_selector,
|
model_selector,
|
||||||
}
|
}
|
||||||
|
@ -55,100 +52,56 @@ impl RenderOnce for Composer {
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(player_avatar)
|
.child(player_avatar)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex().size_full().gap_1().child(
|
||||||
.size_full()
|
v_flex()
|
||||||
.gap_1()
|
.w_full()
|
||||||
.pr_4()
|
.p_4()
|
||||||
.child(
|
.bg(cx.theme().colors().editor_background)
|
||||||
v_flex()
|
.rounded_lg()
|
||||||
.w_full()
|
.child(
|
||||||
.p_4()
|
v_flex()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.justify_between()
|
||||||
.rounded_lg()
|
.w_full()
|
||||||
.child(
|
.gap_2()
|
||||||
v_flex()
|
.child({
|
||||||
.justify_between()
|
let settings = ThemeSettings::get_global(cx);
|
||||||
.w_full()
|
let text_style = TextStyle {
|
||||||
.gap_2()
|
color: cx.theme().colors().editor_foreground,
|
||||||
.child({
|
font_family: settings.buffer_font.family.clone(),
|
||||||
let settings = ThemeSettings::get_global(cx);
|
font_features: settings.buffer_font.features.clone(),
|
||||||
let text_style = TextStyle {
|
font_size: font_size.into(),
|
||||||
color: cx.theme().colors().editor_foreground,
|
font_weight: FontWeight::NORMAL,
|
||||||
font_family: settings.buffer_font.family.clone(),
|
font_style: FontStyle::Normal,
|
||||||
font_features: settings.buffer_font.features.clone(),
|
line_height: line_height.into(),
|
||||||
font_size: font_size.into(),
|
background_color: None,
|
||||||
font_weight: FontWeight::NORMAL,
|
underline: None,
|
||||||
font_style: FontStyle::Normal,
|
strikethrough: None,
|
||||||
line_height: line_height.into(),
|
white_space: WhiteSpace::Normal,
|
||||||
background_color: None,
|
};
|
||||||
underline: None,
|
|
||||||
strikethrough: None,
|
|
||||||
white_space: WhiteSpace::Normal,
|
|
||||||
};
|
|
||||||
|
|
||||||
EditorElement::new(
|
EditorElement::new(
|
||||||
&self.editor,
|
&self.editor,
|
||||||
EditorStyle {
|
EditorStyle {
|
||||||
background: cx.theme().colors().editor_background,
|
background: cx.theme().colors().editor_background,
|
||||||
local_player: cx.theme().players().local(),
|
local_player: cx.theme().players().local(),
|
||||||
text: text_style,
|
text: text_style,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.w_full()
|
.w_full()
|
||||||
.child(
|
.child(h_flex().gap_1().children(
|
||||||
h_flex().gap_1().child(
|
self.tool_registry.status_views().iter().cloned(),
|
||||||
// IconButton/button
|
))
|
||||||
// Toggle - if enabled, .selected(true).selected_style(IconButtonStyle::Filled)
|
.child(h_flex().gap_1().child(self.model_selector)),
|
||||||
//
|
),
|
||||||
// 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()),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,10 +158,18 @@ impl RenderOnce for ModelSelector {
|
||||||
.overflow_x_hidden()
|
.overflow_x_hidden()
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.whitespace_nowrap()
|
.whitespace_nowrap()
|
||||||
.child(Label::new(self.model)),
|
.child(
|
||||||
|
Label::new(self.model)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.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)
|
.style(ButtonStyle::Subtle)
|
||||||
|
|
|
@ -87,6 +87,7 @@ pub enum IconName {
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
ExpandVertical,
|
ExpandVertical,
|
||||||
Close,
|
Close,
|
||||||
|
Code,
|
||||||
Collab,
|
Collab,
|
||||||
Command,
|
Command,
|
||||||
Control,
|
Control,
|
||||||
|
@ -153,6 +154,7 @@ pub enum IconName {
|
||||||
Snip,
|
Snip,
|
||||||
Space,
|
Space,
|
||||||
Split,
|
Split,
|
||||||
|
Spinner,
|
||||||
Tab,
|
Tab,
|
||||||
Terminal,
|
Terminal,
|
||||||
Trash,
|
Trash,
|
||||||
|
@ -191,6 +193,7 @@ impl IconName {
|
||||||
IconName::ChevronUp => "icons/chevron_up.svg",
|
IconName::ChevronUp => "icons/chevron_up.svg",
|
||||||
IconName::ExpandVertical => "icons/expand_vertical.svg",
|
IconName::ExpandVertical => "icons/expand_vertical.svg",
|
||||||
IconName::Close => "icons/x.svg",
|
IconName::Close => "icons/x.svg",
|
||||||
|
IconName::Code => "icons/code.svg",
|
||||||
IconName::Collab => "icons/user_group_16.svg",
|
IconName::Collab => "icons/user_group_16.svg",
|
||||||
IconName::Command => "icons/command.svg",
|
IconName::Command => "icons/command.svg",
|
||||||
IconName::Control => "icons/control.svg",
|
IconName::Control => "icons/control.svg",
|
||||||
|
@ -257,6 +260,7 @@ impl IconName {
|
||||||
IconName::Snip => "icons/snip.svg",
|
IconName::Snip => "icons/snip.svg",
|
||||||
IconName::Space => "icons/space.svg",
|
IconName::Space => "icons/space.svg",
|
||||||
IconName::Split => "icons/split.svg",
|
IconName::Split => "icons/split.svg",
|
||||||
|
IconName::Spinner => "icons/spinner.svg",
|
||||||
IconName::Tab => "icons/tab.svg",
|
IconName::Tab => "icons/tab.svg",
|
||||||
IconName::Terminal => "icons/terminal.svg",
|
IconName::Terminal => "icons/terminal.svg",
|
||||||
IconName::Trash => "icons/trash.svg",
|
IconName::Trash => "icons/trash.svg",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue