From ee1df655692c3003ff09b82a72fa2c91d5f87d32 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Thu, 26 Jun 2025 14:05:59 -0300 Subject: [PATCH] Start displaying messages in new thread element Co-authored-by: Smit Barmase --- crates/agent2/src/acp.rs | 16 +++++++- crates/agent2/src/agent2.rs | 22 ++++++++++ crates/agent2/src/thread_element.rs | 63 +++++++++++++++++++++++++---- 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/crates/agent2/src/acp.rs b/crates/agent2/src/acp.rs index c9c17ecbd7abee1ad0f09a14d04e521cd07d4748..d3327c82b3d9e3ab6cc42c90d4cee1900cc36ba9 100644 --- a/crates/agent2/src/acp.rs +++ b/crates/agent2/src/acp.rs @@ -82,8 +82,22 @@ impl acp::Client for AcpClientDelegate { async fn stream_message_chunk( &self, - chunk: acp::StreamMessageChunkParams, + params: acp::StreamMessageChunkParams, ) -> Result { + let cx = &mut self.cx.clone(); + + cx.update(|cx| { + self.update_thread(¶ms.thread_id.into(), cx, |thread, cx| { + let acp::MessageChunk::Text { chunk } = ¶ms.chunk; + thread.push_assistant_chunk( + MessageChunk::Text { + chunk: chunk.into(), + }, + cx, + ) + }); + })?; + Ok(acp::StreamMessageChunkResponse) } diff --git a/crates/agent2/src/agent2.rs b/crates/agent2/src/agent2.rs index a7c54d6836cd2ecc55a220ca6635864245327e51..d901e416a289555ceb2da8dabb43851f1bad3efc 100644 --- a/crates/agent2/src/agent2.rs +++ b/crates/agent2/src/agent2.rs @@ -219,6 +219,28 @@ impl Thread { cx.notify(); } + pub fn push_assistant_chunk(&mut self, chunk: MessageChunk, cx: &mut Context) { + if let Some(last_entry) = self.entries.last_mut() { + if let AgentThreadEntryContent::Message(Message { + ref mut chunks, + role: Role::Assistant, + }) = last_entry.content + { + chunks.push(chunk); + return; + } + } + + self.entries.push(ThreadEntry { + id: self.next_entry_id.post_inc(), + content: AgentThreadEntryContent::Message(Message { + role: Role::Assistant, + chunks: vec![chunk], + }), + }); + cx.notify(); + } + pub fn send(&mut self, message: Message, cx: &mut Context) -> Task> { let agent = self.agent.clone(); let id = self.id.clone(); diff --git a/crates/agent2/src/thread_element.rs b/crates/agent2/src/thread_element.rs index 9cf936cefd2f1c65ddff9322e50d66a7662de894..09c8d357188482aef1c787802ba480514045ab73 100644 --- a/crates/agent2/src/thread_element.rs +++ b/crates/agent2/src/thread_element.rs @@ -1,21 +1,20 @@ -use std::sync::Arc; - use anyhow::Result; use editor::{Editor, MultiBuffer}; -use gpui::{App, Entity, Focusable, SharedString, Window, div, prelude::*}; +use gpui::{App, Entity, Focusable, SharedString, Subscription, Window, div, prelude::*}; use gpui::{FocusHandle, Task}; use language::Buffer; use ui::Tooltip; use ui::prelude::*; use zed_actions::agent::Chat; -use crate::{Message, MessageChunk, Role, Thread}; +use crate::{AgentThreadEntryContent, Message, MessageChunk, Role, Thread, ThreadEntry}; pub struct ThreadElement { thread: Entity, // todo! use full message editor from agent2 message_editor: Entity, send_task: Option>>, + _subscription: Subscription, } impl ThreadElement { @@ -39,10 +38,15 @@ impl ThreadElement { editor }); + let subscription = cx.observe(&thread, |_, _, cx| { + cx.notify(); + }); + Self { thread, message_editor, send_task: None, + _subscription: subscription, } } @@ -60,18 +64,48 @@ impl ThreadElement { return; } - self.send_task = Some(self.thread.update(cx, |thread, cx| { + let task = self.thread.update(cx, |thread, cx| { let message = Message { role: Role::User, chunks: vec![MessageChunk::Text { chunk: text.into() }], }; thread.send(message, cx) + }); + + self.send_task = Some(cx.spawn(async move |this, cx| { + task.await?; + + this.update(cx, |this, _cx| { + this.send_task.take(); + }) })); self.message_editor.update(cx, |editor, cx| { editor.clear(window, cx); }); } + + fn render_entry( + &self, + entry: &ThreadEntry, + _window: &mut Window, + _cx: &Context, + ) -> AnyElement { + match &entry.content { + AgentThreadEntryContent::Message(message) => div() + .children(message.chunks.iter().map(|chunk| match chunk { + MessageChunk::Text { chunk } => div().child(chunk.clone()), + _ => todo!(), + })) + .into_any(), + AgentThreadEntryContent::ReadFile { path, content: _ } => { + // todo! + div() + .child(format!("", path.display())) + .into_any() + } + } + } } impl Focusable for ThreadElement { @@ -81,7 +115,7 @@ impl Focusable for ThreadElement { } impl Render for ThreadElement { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let text = self.message_editor.read(cx).text(cx); let is_editor_empty = text.is_empty(); let focus_handle = self.message_editor.focus_handle(cx); @@ -89,7 +123,22 @@ impl Render for ThreadElement { v_flex() .key_context("MessageEditor") .on_action(cx.listener(Self::chat)) - .child(div().h_full()) + .child( + v_flex().h_full().gap_1().children( + self.thread + .read(cx) + .entries() + .iter() + .map(|entry| self.render_entry(entry, window, cx)), + ), + ) + .when(self.send_task.is_some(), |this| { + this.child( + Label::new("Generating...") + .color(Color::Muted) + .size(LabelSize::Small), + ) + }) .child( div() .bg(cx.theme().colors().editor_background)