mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-29 11:22:27 +03:00
assistant2: Use ChatMessage
component to render chat messages (#11193)
This PR updates the new assistant panel to use the `ChatMessage` component to render its chat messages. This also lays the foundation for collapsing the messages, though that has yet to be wired up. Adapted from the work on the `assistant-chat-ui` branch. Release Notes: - N/A
This commit is contained in:
parent
ae650342ce
commit
089ea7852d
@ -22,7 +22,6 @@ use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use theme::ThemeSettings;
|
||||
use tools::ProjectIndexTool;
|
||||
use ui::Composer;
|
||||
use util::{paths::EMBEDDINGS_DIR, ResultExt};
|
||||
@ -33,7 +32,7 @@ use workspace::{
|
||||
|
||||
pub use assistant_settings::AssistantSettings;
|
||||
|
||||
use crate::ui::{ChatMessageHeader, UserOrAssistant};
|
||||
use crate::ui::UserOrAssistant;
|
||||
|
||||
const MAX_COMPLETION_CALLS_PER_SUBMISSION: usize = 5;
|
||||
|
||||
@ -526,19 +525,15 @@ impl AssistantChat {
|
||||
let is_last = ix == self.messages.len() - 1;
|
||||
|
||||
match &self.messages[ix] {
|
||||
ChatMessage::User(UserMessage { body, .. }) => div()
|
||||
ChatMessage::User(UserMessage { id, body }) => div()
|
||||
.when(!is_last, |element| element.mb_2())
|
||||
.child(ChatMessageHeader::new(UserOrAssistant::User(
|
||||
self.user_store.read(cx).current_user(),
|
||||
)))
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.text_color(cx.theme().colors().editor_foreground)
|
||||
.font(ThemeSettings::get_global(cx).buffer_font.clone())
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(body.clone()),
|
||||
)
|
||||
.child(crate::ui::ChatMessage::new(
|
||||
*id,
|
||||
UserOrAssistant::User(self.user_store.read(cx).current_user()),
|
||||
body.clone().into_any_element(),
|
||||
false,
|
||||
Box::new(|_, _| {}),
|
||||
))
|
||||
.into_any(),
|
||||
ChatMessage::Assistant(AssistantMessage {
|
||||
id,
|
||||
@ -555,8 +550,14 @@ impl AssistantChat {
|
||||
|
||||
div()
|
||||
.when(!is_last, |element| element.mb_2())
|
||||
.child(ChatMessageHeader::new(UserOrAssistant::Assistant))
|
||||
.child(assistant_body)
|
||||
.child(crate::ui::ChatMessage::new(
|
||||
*id,
|
||||
UserOrAssistant::Assistant,
|
||||
assistant_body.into_any_element(),
|
||||
false,
|
||||
Box::new(|_, _| {}),
|
||||
))
|
||||
// TODO: Should the errors and tool calls get passed into `ChatMessage`?
|
||||
.child(self.render_error(error.clone(), ix, cx))
|
||||
.children(tool_calls.iter().map(|tool_call| {
|
||||
let result = &tool_call.result;
|
||||
|
@ -1,5 +1,5 @@
|
||||
mod chat_message_header;
|
||||
mod chat_message;
|
||||
mod composer;
|
||||
|
||||
pub use chat_message_header::*;
|
||||
pub use chat_message::*;
|
||||
pub use composer::*;
|
||||
|
131
crates/assistant2/src/ui/chat_message.rs
Normal file
131
crates/assistant2/src/ui/chat_message.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use client::User;
|
||||
use gpui::AnyElement;
|
||||
use ui::{prelude::*, Avatar};
|
||||
|
||||
use crate::MessageId;
|
||||
|
||||
pub enum UserOrAssistant {
|
||||
User(Option<Arc<User>>),
|
||||
Assistant,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ChatMessage {
|
||||
id: MessageId,
|
||||
player: UserOrAssistant,
|
||||
message: AnyElement,
|
||||
collapsed: bool,
|
||||
on_collapse: Box<dyn Fn(bool, &mut WindowContext) + 'static>,
|
||||
}
|
||||
|
||||
impl ChatMessage {
|
||||
pub fn new(
|
||||
id: MessageId,
|
||||
player: UserOrAssistant,
|
||||
message: AnyElement,
|
||||
collapsed: bool,
|
||||
on_collapse: Box<dyn Fn(bool, &mut WindowContext) + 'static>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
player,
|
||||
message,
|
||||
collapsed,
|
||||
on_collapse,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ChatMessage {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
// TODO: This should be top padding + 1.5x line height
|
||||
// Set the message height to cut off at exactly 1.5 lines when collapsed
|
||||
let collapsed_height = rems(2.875);
|
||||
|
||||
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(move |_event, cx| (self.on_collapse)(!self.collapsed, cx))
|
||||
.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 = div()
|
||||
.overflow_hidden()
|
||||
.w_full()
|
||||
.p_4()
|
||||
.rounded_lg()
|
||||
.when(self.collapsed, |this| this.h(collapsed_height))
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.child(self.message);
|
||||
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(ChatMessageHeader::new(self.player))
|
||||
.child(h_flex().gap_3().child(collapse_handle).child(content))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct ChatMessageHeader {
|
||||
player: UserOrAssistant,
|
||||
contexts: Vec<()>,
|
||||
}
|
||||
|
||||
impl ChatMessageHeader {
|
||||
fn new(player: UserOrAssistant) -> Self {
|
||||
Self {
|
||||
player,
|
||||
contexts: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ChatMessageHeader {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
let (username, avatar_uri) = match self.player {
|
||||
UserOrAssistant::Assistant => (
|
||||
"Assistant".into(),
|
||||
Some("https://zed.dev/assistant_avatar.png".into()),
|
||||
),
|
||||
UserOrAssistant::User(Some(user)) => {
|
||||
(user.github_login.clone(), Some(user.avatar_uri.clone()))
|
||||
}
|
||||
UserOrAssistant::User(None) => ("You".into(), None),
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.map(|this| {
|
||||
let avatar_size = rems(20.0 / 16.0);
|
||||
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)),
|
||||
)
|
||||
.child(div().when(!self.contexts.is_empty(), |this| {
|
||||
this.child(Label::new(self.contexts.len().to_string()).color(Color::Muted))
|
||||
}))
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
use client::User;
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, Avatar};
|
||||
|
||||
pub enum UserOrAssistant {
|
||||
User(Option<Arc<User>>),
|
||||
Assistant,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ChatMessageHeader {
|
||||
player: UserOrAssistant,
|
||||
contexts: Vec<()>,
|
||||
}
|
||||
|
||||
impl ChatMessageHeader {
|
||||
pub fn new(player: UserOrAssistant) -> Self {
|
||||
Self {
|
||||
player,
|
||||
contexts: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ChatMessageHeader {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
let (username, avatar_uri) = match self.player {
|
||||
UserOrAssistant::Assistant => (
|
||||
"Assistant".into(),
|
||||
Some("https://zed.dev/assistant_avatar.png".into()),
|
||||
),
|
||||
UserOrAssistant::User(Some(user)) => {
|
||||
(user.github_login.clone(), Some(user.avatar_uri.clone()))
|
||||
}
|
||||
UserOrAssistant::User(None) => ("You".into(), None),
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.map(|this| {
|
||||
let avatar_size = rems(20.0 / 16.0);
|
||||
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)),
|
||||
)
|
||||
.child(div().when(!self.contexts.is_empty(), |this| {
|
||||
this.child(Label::new(self.contexts.len().to_string()).color(Color::Muted))
|
||||
}))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user