chat_message.rs

  1use std::sync::Arc;
  2
  3use client::User;
  4use gpui::AnyElement;
  5use ui::{prelude::*, Avatar};
  6
  7use crate::MessageId;
  8
  9pub enum UserOrAssistant {
 10    User(Option<Arc<User>>),
 11    Assistant,
 12}
 13
 14#[derive(IntoElement)]
 15pub struct ChatMessage {
 16    id: MessageId,
 17    player: UserOrAssistant,
 18    message: AnyElement,
 19    collapsed: bool,
 20    on_collapse: Box<dyn Fn(bool, &mut WindowContext) + 'static>,
 21}
 22
 23impl ChatMessage {
 24    pub fn new(
 25        id: MessageId,
 26        player: UserOrAssistant,
 27        message: AnyElement,
 28        collapsed: bool,
 29        on_collapse: Box<dyn Fn(bool, &mut WindowContext) + 'static>,
 30    ) -> Self {
 31        Self {
 32            id,
 33            player,
 34            message,
 35            collapsed,
 36            on_collapse,
 37        }
 38    }
 39}
 40
 41impl RenderOnce for ChatMessage {
 42    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
 43        // TODO: This should be top padding + 1.5x line height
 44        // Set the message height to cut off at exactly 1.5 lines when collapsed
 45        let collapsed_height = rems(2.875);
 46
 47        let collapse_handle_id = SharedString::from(format!("{}_collapse_handle", self.id.0));
 48        let collapse_handle = h_flex()
 49            .id(collapse_handle_id.clone())
 50            .group(collapse_handle_id.clone())
 51            .flex_none()
 52            .justify_center()
 53            .w_1()
 54            .mx_2()
 55            .h_full()
 56            .on_click(move |_event, cx| (self.on_collapse)(!self.collapsed, cx))
 57            .child(
 58                div()
 59                    .w_px()
 60                    .h_full()
 61                    .rounded_lg()
 62                    .overflow_hidden()
 63                    .bg(cx.theme().colors().element_background)
 64                    .group_hover(collapse_handle_id, |this| {
 65                        this.bg(cx.theme().colors().element_hover)
 66                    }),
 67            );
 68        let content = div()
 69            .overflow_hidden()
 70            .w_full()
 71            .p_4()
 72            .rounded_lg()
 73            .when(self.collapsed, |this| this.h(collapsed_height))
 74            .bg(cx.theme().colors().surface_background)
 75            .child(self.message);
 76
 77        v_flex()
 78            .gap_1()
 79            .child(ChatMessageHeader::new(self.player))
 80            .child(h_flex().gap_3().child(collapse_handle).child(content))
 81    }
 82}
 83
 84#[derive(IntoElement)]
 85struct ChatMessageHeader {
 86    player: UserOrAssistant,
 87    contexts: Vec<()>,
 88}
 89
 90impl ChatMessageHeader {
 91    fn new(player: UserOrAssistant) -> Self {
 92        Self {
 93            player,
 94            contexts: Vec::new(),
 95        }
 96    }
 97}
 98
 99impl RenderOnce for ChatMessageHeader {
100    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
101        let (username, avatar_uri) = match self.player {
102            UserOrAssistant::Assistant => (
103                "Assistant".into(),
104                Some("https://zed.dev/assistant_avatar.png".into()),
105            ),
106            UserOrAssistant::User(Some(user)) => {
107                (user.github_login.clone(), Some(user.avatar_uri.clone()))
108            }
109            UserOrAssistant::User(None) => ("You".into(), None),
110        };
111
112        h_flex()
113            .justify_between()
114            .child(
115                h_flex()
116                    .gap_3()
117                    .map(|this| {
118                        let avatar_size = rems(20.0 / 16.0);
119                        if let Some(avatar_uri) = avatar_uri {
120                            this.child(Avatar::new(avatar_uri).size(avatar_size))
121                        } else {
122                            this.child(div().size(avatar_size))
123                        }
124                    })
125                    .child(Label::new(username).color(Color::Default)),
126            )
127            .child(div().when(!self.contexts.is_empty(), |this| {
128                this.child(Label::new(self.contexts.len().to_string()).color(Color::Muted))
129            }))
130    }
131}