crates/assistant2/evals/list-of-into-element.md 🔗
@@ -0,0 +1 @@
+> Give me a comprehensive list of all the elements define in my project (impl Element for {}, impl<T: 'static> Element for {}, impl IntoElement for {})
Nate Butler , Marshall Bowers , and Kyle Kelley created
Release Notes:
- N/A
---------
Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
Co-authored-by: Kyle Kelley <rgbkrk@gmail.com>
crates/assistant2/evals/list-of-into-element.md | 1
crates/assistant2/evals/new-gpui-element.md | 1
crates/assistant2/evals/what-is-the-assistant2-crate.md | 1
crates/assistant2/src/assistant2.rs | 103 +++---
crates/assistant2/src/ui/chat_message.rs | 164 +++++-----
crates/assistant2/src/ui/composer.rs | 120 ++++---
6 files changed, 210 insertions(+), 180 deletions(-)
@@ -0,0 +1 @@
+> Give me a comprehensive list of all the elements define in my project (impl Element for {}, impl<T: 'static> Element for {}, impl IntoElement for {})
@@ -0,0 +1 @@
+> What are all the places we define a new gpui element in my project? (impl Element for {})
@@ -0,0 +1 @@
+> Can you tell me what the assistant2 crate is for in my project? Tell me in 100 words or less.
@@ -182,8 +182,7 @@ impl Render for AssistantPanel {
div()
.size_full()
.v_flex()
- .p_2()
- .bg(cx.theme().colors().background)
+ .bg(cx.theme().colors().panel_background)
.child(self.chat.clone())
}
}
@@ -634,8 +633,13 @@ impl AssistantChat {
}
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
+ let is_first = ix == 0;
let is_last = ix == self.messages.len() - 1;
+ let padding = Spacing::Large.rems(cx);
+
+ // Whenever there's a run of assistant messages, group as one Assistant UI element
+
match &self.messages[ix] {
ChatMessage::User(UserMessage {
id,
@@ -643,7 +647,7 @@ impl AssistantChat {
attachments,
}) => div()
.id(SharedString::from(format!("message-{}-container", id.0)))
- .when(!is_last, |element| element.mb_2())
+ .when(is_first, |this| this.pt(padding))
.map(|element| {
if self.editing_message_id() == Some(*id) {
element.child(Composer::new(
@@ -672,35 +676,39 @@ impl AssistantChat {
}
}
}))
- .child(crate::ui::ChatMessage::new(
- *id,
- UserOrAssistant::User(self.user_store.read(cx).current_user()),
- Some(
- RichText::new(
- body.read(cx).text(cx),
- &[],
- &self.language_registry,
- )
- .element(ElementId::from(id.0), cx),
- ),
- Some(
- h_flex()
- .gap_2()
- .children(
- attachments
- .iter()
- .map(|attachment| attachment.view.clone()),
+ .child(
+ crate::ui::ChatMessage::new(
+ *id,
+ UserOrAssistant::User(self.user_store.read(cx).current_user()),
+ Some(
+ RichText::new(
+ body.read(cx).text(cx),
+ &[],
+ &self.language_registry,
)
- .into_any_element(),
- ),
- self.is_message_collapsed(id),
- Box::new(cx.listener({
- let id = *id;
- move |assistant_chat, _event, _cx| {
- assistant_chat.toggle_message_collapsed(id)
- }
- })),
- ))
+ .element(ElementId::from(id.0), cx),
+ ),
+ Some(
+ h_flex()
+ .gap_2()
+ .children(
+ attachments
+ .iter()
+ .map(|attachment| attachment.view.clone()),
+ )
+ .into_any_element(),
+ ),
+ self.is_message_collapsed(id),
+ Box::new(cx.listener({
+ let id = *id;
+ move |assistant_chat, _event, _cx| {
+ assistant_chat.toggle_message_collapsed(id)
+ }
+ })),
+ )
+ // TODO: Wire up selections.
+ .selected(is_last),
+ )
}
})
.into_any(),
@@ -716,7 +724,6 @@ impl AssistantChat {
} else {
Some(
div()
- .p_2()
.child(body.element(ElementId::from(id.0), cx))
.into_any_element(),
)
@@ -734,20 +741,24 @@ impl AssistantChat {
};
div()
- .when(!is_last, |element| element.mb_2())
- .child(crate::ui::ChatMessage::new(
- *id,
- UserOrAssistant::Assistant,
- assistant_body,
- tools_body,
- self.is_message_collapsed(id),
- Box::new(cx.listener({
- let id = *id;
- move |assistant_chat, _event, _cx| {
- assistant_chat.toggle_message_collapsed(id)
- }
- })),
- ))
+ .when(is_first, |this| this.pt(padding))
+ .child(
+ crate::ui::ChatMessage::new(
+ *id,
+ UserOrAssistant::Assistant,
+ assistant_body,
+ tools_body,
+ self.is_message_collapsed(id),
+ Box::new(cx.listener({
+ let id = *id;
+ move |assistant_chat, _event, _cx| {
+ assistant_chat.toggle_message_collapsed(id)
+ }
+ })),
+ )
+ // TODO: Wire up selections.
+ .selected(is_last),
+ )
.child(self.render_error(error.clone(), ix, cx))
.into_any()
}
@@ -1,8 +1,8 @@
use std::sync::Arc;
use client::User;
-use gpui::{AnyElement, ClickEvent};
-use ui::{prelude::*, Avatar};
+use gpui::{hsla, AnyElement, ClickEvent};
+use ui::{prelude::*, Avatar, Tooltip};
use crate::MessageId;
@@ -17,6 +17,7 @@ pub struct ChatMessage {
player: UserOrAssistant,
message: Option<AnyElement>,
tools_used: Option<AnyElement>,
+ selected: bool,
collapsed: bool,
on_collapse_handle_click: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
}
@@ -35,79 +36,36 @@ impl ChatMessage {
player,
message,
tools_used,
+ selected: false,
collapsed,
on_collapse_handle_click,
}
}
}
+impl Selectable for ChatMessage {
+ fn selected(mut self, selected: bool) -> Self {
+ self.selected = selected;
+ self
+ }
+}
+
impl RenderOnce for ChatMessage {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let message_group = SharedString::from(format!("{}_group", self.id.0));
+
let collapse_handle_id = SharedString::from(format!("{}_collapse_handle", self.id.0));
- let collapse_handle = h_flex()
- .id(collapse_handle_id.clone())
- .group(collapse_handle_id.clone())
- .flex_none()
- .justify_center()
- .w_1()
- .mx_2()
- .h_full()
- .on_click(self.on_collapse_handle_click)
- .child(
- div()
- .w_px()
- .h_full()
- .rounded_lg()
- .overflow_hidden()
- .bg(cx.theme().colors().element_background)
- .group_hover(collapse_handle_id, |this| {
- this.bg(cx.theme().colors().element_hover)
- }),
- );
- let content_padding = rems(1.);
+ let content_padding = Spacing::Small.rems(cx);
// Clamp the message height to exactly 1.5 lines when collapsed.
let collapsed_height = content_padding.to_pixels(cx.rem_size()) + cx.line_height() * 1.5;
- v_flex()
- .gap_1()
- .child(ChatMessageHeader::new(self.player))
- .when(self.message.is_some() || self.tools_used.is_some(), |el| {
- el.child(
- h_flex().gap_3().child(collapse_handle).child(
- div()
- .overflow_hidden()
- .w_full()
- .p(content_padding)
- .gap_3()
- .rounded_lg()
- .when(self.collapsed, |this| this.h(collapsed_height))
- .bg(cx.theme().colors().surface_background)
- .children(self.message)
- .children(self.tools_used),
- ),
- )
- })
- }
-}
-
-#[derive(IntoElement)]
-struct ChatMessageHeader {
- player: UserOrAssistant,
- contexts: Vec<()>,
-}
-
-impl ChatMessageHeader {
- fn new(player: UserOrAssistant) -> Self {
- Self {
- player,
- contexts: Vec::new(),
- }
- }
-}
+ let background_color = if let UserOrAssistant::User(_) = &self.player {
+ Some(cx.theme().colors().surface_background)
+ } else {
+ None
+ };
-impl RenderOnce for ChatMessageHeader {
- fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let (username, avatar_uri) = match self.player {
UserOrAssistant::Assistant => (
"Assistant".into(),
@@ -119,23 +77,77 @@ impl RenderOnce for ChatMessageHeader {
UserOrAssistant::User(None) => ("You".into(), None),
};
- h_flex()
- .justify_between()
+ v_flex()
+ .group(message_group.clone())
+ .gap(Spacing::XSmall.rems(cx))
+ .p(Spacing::XSmall.rems(cx))
+ .when(self.selected, |element| {
+ element.bg(hsla(0.6, 0.67, 0.46, 0.12))
+ })
+ .rounded_lg()
.child(
h_flex()
- .gap_3()
- .map(|this| {
- let avatar_size = rems_from_px(20.);
- if let Some(avatar_uri) = avatar_uri {
- this.child(Avatar::new(avatar_uri).size(avatar_size))
- } else {
- this.child(div().size(avatar_size))
- }
- })
- .child(Label::new(username).color(Color::Default)),
+ .justify_between()
+ .px(content_padding)
+ .child(
+ h_flex()
+ .gap_2()
+ .map(|this| {
+ let avatar_size = rems_from_px(20.);
+ if let Some(avatar_uri) = avatar_uri {
+ this.child(Avatar::new(avatar_uri).size(avatar_size))
+ } else {
+ this.child(div().size(avatar_size))
+ }
+ })
+ .child(Label::new(username).color(Color::Muted)),
+ )
+ .child(
+ h_flex().visible_on_hover(message_group).child(
+ // temp icons
+ IconButton::new(
+ collapse_handle_id.clone(),
+ if self.collapsed {
+ IconName::ArrowUp
+ } else {
+ IconName::ArrowDown
+ },
+ )
+ .icon_size(IconSize::XSmall)
+ .icon_color(Color::Muted)
+ .on_click(self.on_collapse_handle_click)
+ .tooltip(|cx| Tooltip::text("Collapse Message", cx)),
+ ), // .child(
+ // IconButton::new("copy-message", IconName::Copy)
+ // .icon_color(Color::Muted)
+ // .icon_size(IconSize::XSmall),
+ // )
+ // .child(
+ // IconButton::new("menu", IconName::Ellipsis)
+ // .icon_color(Color::Muted)
+ // .icon_size(IconSize::XSmall),
+ // ),
+ ),
)
- .child(div().when(!self.contexts.is_empty(), |this| {
- this.child(Label::new(self.contexts.len().to_string()).color(Color::Muted))
- }))
+ .when(self.message.is_some() || self.tools_used.is_some(), |el| {
+ el.child(
+ h_flex().child(
+ v_flex()
+ .relative()
+ .overflow_hidden()
+ .w_full()
+ .p(content_padding)
+ .gap_3()
+ .text_ui(cx)
+ .rounded_lg()
+ .when_some(background_color, |this, background_color| {
+ this.bg(background_color)
+ })
+ .when(self.collapsed, |this| this.h(collapsed_height))
+ .children(self.message)
+ .when_some(self.tools_used, |this, tools_used| this.child(tools_used)),
+ ),
+ )
+ })
}
}
@@ -6,7 +6,7 @@ use editor::{Editor, EditorElement, EditorStyle};
use gpui::{AnyElement, FontStyle, FontWeight, TextStyle, View, WeakView, WhiteSpace};
use settings::Settings;
use theme::ThemeSettings;
-use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, Divider, Tooltip};
+use ui::{popover_menu, prelude::*, ButtonLike, ContextMenu, Divider, TextSize, Tooltip};
#[derive(IntoElement)]
pub struct Composer {
@@ -50,67 +50,71 @@ impl Composer {
impl RenderOnce for Composer {
fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
- let font_size = rems(0.875);
+ let font_size = TextSize::Default.rems(cx);
let line_height = font_size.to_pixels(cx.rem_size()) * 1.3;
- h_flex().w_full().items_start().mt_2().child(
- v_flex().size_full().gap_1().child(
- v_flex()
- .w_full()
- .p_3()
- .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,
- };
+ h_flex()
+ .p(Spacing::Small.rems(cx))
+ .w_full()
+ .items_start()
+ .child(
+ v_flex().size_full().gap_1().child(
+ v_flex()
+ .w_full()
+ .p_3()
+ .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(
- h_flex()
- .gap_2()
- .child(self.render_tools(cx))
- .child(Divider::vertical())
- .child(self.render_attachment_tools(cx)),
- ),
+ 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().gap_1().child(self.model_selector)),
- ),
- ),
- ),
- )
+ })
+ .child(
+ h_flex()
+ .flex_none()
+ .gap_2()
+ .justify_between()
+ .w_full()
+ .child(
+ h_flex().gap_1().child(
+ h_flex()
+ .gap_2()
+ .child(self.render_tools(cx))
+ .child(Divider::vertical())
+ .child(self.render_attachment_tools(cx)),
+ ),
+ )
+ .child(h_flex().gap_1().child(self.model_selector)),
+ ),
+ ),
+ ),
+ )
}
}