chat_message.rs

  1use std::sync::Arc;
  2
  3use client::User;
  4use gpui::{hsla, AnyElement, ClickEvent};
  5use ui::{prelude::*, Avatar, Tooltip};
  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    messages: Vec<AnyElement>,
 19    selected: bool,
 20    collapsed: bool,
 21    on_collapse_handle_click: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
 22}
 23
 24impl ChatMessage {
 25    pub fn new(
 26        id: MessageId,
 27        player: UserOrAssistant,
 28        messages: Vec<AnyElement>,
 29        collapsed: bool,
 30        on_collapse_handle_click: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
 31    ) -> Self {
 32        Self {
 33            id,
 34            player,
 35            messages,
 36            selected: false,
 37            collapsed,
 38            on_collapse_handle_click,
 39        }
 40    }
 41}
 42
 43impl Selectable for ChatMessage {
 44    fn selected(mut self, selected: bool) -> Self {
 45        self.selected = selected;
 46        self
 47    }
 48}
 49
 50impl RenderOnce for ChatMessage {
 51    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
 52        let message_group = SharedString::from(format!("{}_group", self.id.0));
 53
 54        let collapse_handle_id = SharedString::from(format!("{}_collapse_handle", self.id.0));
 55
 56        let content_padding = Spacing::Small.rems(cx);
 57        // Clamp the message height to exactly 1.5 lines when collapsed.
 58        let collapsed_height = content_padding.to_pixels(cx.rem_size()) + cx.line_height() * 1.5;
 59
 60        let background_color = if let UserOrAssistant::User(_) = &self.player {
 61            Some(cx.theme().colors().surface_background)
 62        } else {
 63            None
 64        };
 65
 66        let (username, avatar_uri) = match self.player {
 67            UserOrAssistant::Assistant => (
 68                "Assistant".into(),
 69                Some("https://zed.dev/assistant_avatar.png".into()),
 70            ),
 71            UserOrAssistant::User(Some(user)) => {
 72                (user.github_login.clone(), Some(user.avatar_uri.clone()))
 73            }
 74            UserOrAssistant::User(None) => ("You".into(), None),
 75        };
 76
 77        v_flex()
 78            .group(message_group.clone())
 79            .gap(Spacing::XSmall.rems(cx))
 80            .p(Spacing::XSmall.rems(cx))
 81            .when(self.selected, |element| {
 82                element.bg(hsla(0.6, 0.67, 0.46, 0.12))
 83            })
 84            .rounded_lg()
 85            .child(
 86                h_flex()
 87                    .justify_between()
 88                    .px(content_padding)
 89                    .child(
 90                        h_flex()
 91                            .gap_2()
 92                            .map(|this| {
 93                                let avatar_size = rems_from_px(20.);
 94                                if let Some(avatar_uri) = avatar_uri {
 95                                    this.child(Avatar::new(avatar_uri).size(avatar_size))
 96                                } else {
 97                                    this.child(div().size(avatar_size))
 98                                }
 99                            })
100                            .child(Label::new(username).color(Color::Muted)),
101                    )
102                    .child(
103                        h_flex().visible_on_hover(message_group).child(
104                            // temp icons
105                            IconButton::new(
106                                collapse_handle_id.clone(),
107                                if self.collapsed {
108                                    IconName::ArrowUp
109                                } else {
110                                    IconName::ArrowDown
111                                },
112                            )
113                            .icon_size(IconSize::XSmall)
114                            .icon_color(Color::Muted)
115                            .on_click(self.on_collapse_handle_click)
116                            .tooltip(|cx| Tooltip::text("Collapse Message", cx)),
117                        ),
118                    ),
119            )
120            .when(self.messages.len() > 0, |el| {
121                el.child(
122                    h_flex().w_full().child(
123                        v_flex()
124                            .relative()
125                            .overflow_hidden()
126                            .w_full()
127                            .p(content_padding)
128                            .gap_3()
129                            .text_ui(cx)
130                            .rounded_lg()
131                            .when_some(background_color, |this, background_color| {
132                                this.bg(background_color)
133                            })
134                            .when(self.collapsed, |this| this.h(collapsed_height))
135                            .children(self.messages),
136                    ),
137                )
138            })
139    }
140}