assistant2: Render messages in the thread using a list (#21491)

This PR updates the rendering of the messages in the current thread to
use a `gpui::list`.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-12-03 16:25:09 -05:00 committed by GitHub
parent db34f29300
commit aca23da971
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 57 additions and 40 deletions

View file

@ -4,9 +4,9 @@ use anyhow::Result;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use client::zed_urls; use client::zed_urls;
use gpui::{ use gpui::{
prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, EventEmitter, FocusHandle, list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, Empty, EventEmitter,
FocusableView, FontWeight, Model, Pixels, Subscription, Task, View, ViewContext, WeakView, FocusHandle, FocusableView, FontWeight, ListAlignment, ListState, Model, Pixels, Subscription,
WindowContext, Task, View, ViewContext, WeakView, WindowContext,
}; };
use language_model::{LanguageModelRegistry, Role}; use language_model::{LanguageModelRegistry, Role};
use language_model_selector::LanguageModelSelector; use language_model_selector::LanguageModelSelector;
@ -15,7 +15,7 @@ use workspace::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace; use workspace::Workspace;
use crate::message_editor::MessageEditor; use crate::message_editor::MessageEditor;
use crate::thread::{Message, Thread, ThreadError, ThreadEvent}; use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{NewThread, ToggleFocus, ToggleModelSelector}; use crate::{NewThread, ToggleFocus, ToggleModelSelector};
@ -35,6 +35,8 @@ pub struct AssistantPanel {
#[allow(unused)] #[allow(unused)]
thread_store: Model<ThreadStore>, thread_store: Model<ThreadStore>,
thread: Model<Thread>, thread: Model<Thread>,
thread_messages: Vec<MessageId>,
thread_list_state: ListState,
message_editor: View<MessageEditor>, message_editor: View<MessageEditor>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
last_error: Option<ThreadError>, last_error: Option<ThreadError>,
@ -77,6 +79,14 @@ impl AssistantPanel {
workspace: workspace.weak_handle(), workspace: workspace.weak_handle(),
thread_store, thread_store,
thread: thread.clone(), thread: thread.clone(),
thread_messages: Vec::new(),
thread_list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
let this = cx.view().downgrade();
move |ix, cx: &mut WindowContext| {
this.update(cx, |this, cx| this.render_message(ix, cx))
.unwrap()
}
}),
message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)), message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)),
tools, tools,
last_error: None, last_error: None,
@ -110,6 +120,12 @@ impl AssistantPanel {
self.last_error = Some(error.clone()); self.last_error = Some(error.clone());
} }
ThreadEvent::StreamedCompletion => {} ThreadEvent::StreamedCompletion => {}
ThreadEvent::MessageAdded(message_id) => {
let old_len = self.thread_messages.len();
self.thread_messages.push(*message_id);
self.thread_list_state.splice(old_len..old_len, 1);
cx.notify();
}
ThreadEvent::UsePendingTools => { ThreadEvent::UsePendingTools => {
let pending_tool_uses = self let pending_tool_uses = self
.thread .thread
@ -301,31 +317,42 @@ impl AssistantPanel {
) )
} }
fn render_message(&self, message: Message, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let message_id = self.thread_messages[ix];
let Some(message) = self.thread.read(cx).message(message_id) else {
return Empty.into_any();
};
let (role_icon, role_name) = match message.role { let (role_icon, role_name) = match message.role {
Role::User => (IconName::Person, "You"), Role::User => (IconName::Person, "You"),
Role::Assistant => (IconName::ZedAssistant, "Assistant"), Role::Assistant => (IconName::ZedAssistant, "Assistant"),
Role::System => (IconName::Settings, "System"), Role::System => (IconName::Settings, "System"),
}; };
v_flex() div()
.border_1() .id(("message-container", ix))
.border_color(cx.theme().colors().border_variant) .p_2()
.rounded_md()
.child( .child(
h_flex() v_flex()
.justify_between() .border_1()
.p_1p5()
.border_b_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.rounded_md()
.child( .child(
h_flex() h_flex()
.gap_2() .justify_between()
.child(Icon::new(role_icon).size(IconSize::Small)) .p_1p5()
.child(Label::new(role_name).size(LabelSize::Small)), .border_b_1()
), .border_color(cx.theme().colors().border_variant)
.child(
h_flex()
.gap_2()
.child(Icon::new(role_icon).size(IconSize::Small))
.child(Label::new(role_name).size(LabelSize::Small)),
),
)
.child(v_flex().p_1p5().child(Label::new(message.text.clone()))),
) )
.child(v_flex().p_1p5().child(Label::new(message.text.clone()))) .into_any()
} }
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> { fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
@ -477,8 +504,6 @@ impl AssistantPanel {
impl Render for AssistantPanel { impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let messages = self.thread.read(cx).messages().cloned().collect::<Vec<_>>();
v_flex() v_flex()
.key_context("AssistantPanel2") .key_context("AssistantPanel2")
.justify_between() .justify_between()
@ -487,20 +512,7 @@ impl Render for AssistantPanel {
this.new_thread(cx); this.new_thread(cx);
})) }))
.child(self.render_toolbar(cx)) .child(self.render_toolbar(cx))
.child( .child(list(self.thread_list_state.clone()).flex_1())
v_flex()
.id("message-list")
.gap_2()
.size_full()
.p_2()
.overflow_y_scroll()
.bg(cx.theme().colors().panel_background)
.children(
messages
.into_iter()
.map(|message| self.render_message(message, cx)),
),
)
.child( .child(
h_flex() h_flex()
.border_t_1() .border_t_1()

View file

@ -56,7 +56,7 @@ impl MessageEditor {
}); });
self.thread.update(cx, |thread, cx| { self.thread.update(cx, |thread, cx| {
thread.insert_user_message(user_message); thread.insert_user_message(user_message, cx);
let mut request = thread.to_completion_request(request_kind, cx); let mut request = thread.to_completion_request(request_kind, cx);
if self.use_tools { if self.use_tools {

View file

@ -63,8 +63,8 @@ impl Thread {
} }
} }
pub fn messages(&self) -> impl Iterator<Item = &Message> { pub fn message(&self, id: MessageId) -> Option<&Message> {
self.messages.iter() self.messages.iter().find(|message| message.id == id)
} }
pub fn tools(&self) -> &Arc<ToolWorkingSet> { pub fn tools(&self) -> &Arc<ToolWorkingSet> {
@ -75,12 +75,14 @@ impl Thread {
self.pending_tool_uses_by_id.values().collect() self.pending_tool_uses_by_id.values().collect()
} }
pub fn insert_user_message(&mut self, text: impl Into<String>) { pub fn insert_user_message(&mut self, text: impl Into<String>, cx: &mut ModelContext<Self>) {
let id = self.next_message_id.post_inc();
self.messages.push(Message { self.messages.push(Message {
id: self.next_message_id.post_inc(), id,
role: Role::User, role: Role::User,
text: text.into(), text: text.into(),
}); });
cx.emit(ThreadEvent::MessageAdded(id));
} }
pub fn to_completion_request( pub fn to_completion_request(
@ -150,11 +152,13 @@ impl Thread {
thread.update(&mut cx, |thread, cx| { thread.update(&mut cx, |thread, cx| {
match event { match event {
LanguageModelCompletionEvent::StartMessage { .. } => { LanguageModelCompletionEvent::StartMessage { .. } => {
let id = thread.next_message_id.post_inc();
thread.messages.push(Message { thread.messages.push(Message {
id: thread.next_message_id.post_inc(), id,
role: Role::Assistant, role: Role::Assistant,
text: String::new(), text: String::new(),
}); });
cx.emit(ThreadEvent::MessageAdded(id));
} }
LanguageModelCompletionEvent::Stop(reason) => { LanguageModelCompletionEvent::Stop(reason) => {
stop_reason = reason; stop_reason = reason;
@ -316,6 +320,7 @@ pub enum ThreadError {
pub enum ThreadEvent { pub enum ThreadEvent {
ShowError(ThreadError), ShowError(ThreadError),
StreamedCompletion, StreamedCompletion,
MessageAdded(MessageId),
UsePendingTools, UsePendingTools,
ToolFinished { ToolFinished {
#[allow(unused)] #[allow(unused)]