diff --git a/Cargo.lock b/Cargo.lock index 6047a8c25f2d497ce766512407fb851b29e54ecb..dee38013741c72842363793ab9f363daaca4c448 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,7 @@ dependencies = [ "base64 0.22.1", "chrono", "collections", + "editor", "env_logger 0.11.8", "futures 0.3.31", "gpui", @@ -126,9 +127,11 @@ dependencies = [ "serde_json", "settings", "smol", + "ui", "util", "uuid", "workspace-hack", + "zed_actions", ] [[package]] diff --git a/crates/agent2/Cargo.toml b/crates/agent2/Cargo.toml index bcb4379b67482a20c14038fb29c69dc527a5a98e..22ff0373fde1ac18f8d74ec4bc1d883ec7ea7f63 100644 --- a/crates/agent2/Cargo.toml +++ b/crates/agent2/Cargo.toml @@ -25,15 +25,18 @@ async-trait.workspace = true base64.workspace = true chrono.workspace = true collections.workspace = true +editor.workspace = true futures.workspace = true gpui.workspace = true language.workspace = true parking_lot.workspace = true project.workspace = true smol.workspace = true +ui.workspace = true util.workspace = true uuid.workspace = true workspace-hack.workspace = true +zed_actions.workspace = true [dev-dependencies] env_logger.workspace = true diff --git a/crates/agent2/src/thread_element.rs b/crates/agent2/src/thread_element.rs index e5cef3d795c0817686e3935f4cc0e96bd2783754..9cf936cefd2f1c65ddff9322e50d66a7662de894 100644 --- a/crates/agent2/src/thread_element.rs +++ b/crates/agent2/src/thread_element.rs @@ -1,27 +1,124 @@ -use gpui::{App, Entity, SharedString, Window, div, prelude::*}; +use std::sync::Arc; -use crate::Thread; +use anyhow::Result; +use editor::{Editor, MultiBuffer}; +use gpui::{App, Entity, Focusable, SharedString, 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}; pub struct ThreadElement { thread: Entity, + // todo! use full message editor from agent2 + message_editor: Entity, + send_task: Option>>, } impl ThreadElement { - pub fn new(thread: Entity) -> Self { - Self { thread } + pub fn new(thread: Entity, window: &mut Window, cx: &mut Context) -> Self { + let message_editor = cx.new(|cx| { + let buffer = cx.new(|cx| Buffer::local("", cx)); + let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); + + let mut editor = Editor::new( + editor::EditorMode::AutoHeight { + min_lines: 5, + max_lines: None, + }, + buffer, + None, + window, + cx, + ); + editor.set_placeholder_text("Send a message", cx); + editor.set_soft_wrap(); + editor + }); + + Self { + thread, + message_editor, + send_task: None, + } } pub fn title(&self, cx: &App) -> SharedString { self.thread.read(cx).title() } - pub fn cancel(&self, window: &mut Window, cx: &mut Context) { - // todo! + pub fn cancel(&mut self) { + self.send_task.take(); + } + + fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context) { + let text = self.message_editor.read(cx).text(cx); + if text.is_empty() { + return; + } + + self.send_task = Some(self.thread.update(cx, |thread, cx| { + let message = Message { + role: Role::User, + chunks: vec![MessageChunk::Text { chunk: text.into() }], + }; + thread.send(message, cx) + })); + + self.message_editor.update(cx, |editor, cx| { + editor.clear(window, cx); + }); + } +} + +impl Focusable for ThreadElement { + fn focus_handle(&self, cx: &App) -> FocusHandle { + self.message_editor.focus_handle(cx) } } impl Render for ThreadElement { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - div().child("agent 2") + 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); + + v_flex() + .key_context("MessageEditor") + .on_action(cx.listener(Self::chat)) + .child(div().h_full()) + .child( + div() + .bg(cx.theme().colors().editor_background) + .border_t_1() + .border_color(cx.theme().colors().border) + .p_2() + .child(self.message_editor.clone()), + ) + .child( + h_flex().p_2().justify_end().child( + IconButton::new("send-message", IconName::Send) + .icon_color(Color::Accent) + .style(ButtonStyle::Filled) + .disabled(is_editor_empty) + .on_click({ + let focus_handle = focus_handle.clone(); + move |_event, window, cx| { + focus_handle.dispatch_action(&Chat, window, cx); + } + }) + .when(!is_editor_empty, |button| { + button.tooltip(move |window, cx| { + Tooltip::for_action("Send", &Chat, window, cx) + }) + }) + .when(is_editor_empty, |button| { + button.tooltip(Tooltip::text("Type a message to submit")) + }), + ), + ) } } diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 5e1ffb94c39fbe1635337d79ad0a8c930362175d..7ca0057c738c3357a93c41eb2ed31ac5db49fda9 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -754,7 +754,7 @@ impl AgentPanel { thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx)); } ActiveView::Agent2Thread { thread_element, .. } => { - thread_element.update(cx, |thread_element, cx| thread_element.cancel(window, cx)); + thread_element.update(cx, |thread_element, _cx| thread_element.cancel()); } ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {} } @@ -919,7 +919,8 @@ impl AgentPanel { cx.spawn_in(window, async move |this, cx| { let agent = AcpAgent::stdio(child, project, cx); let thread = agent.create_thread(cx).await?; - let thread_element = cx.new(|_cx| agent2::ThreadElement::new(thread))?; + let thread_element = + cx.new_window_entity(|window, cx| agent2::ThreadElement::new(thread, window, cx))?; this.update_in(cx, |this, window, cx| { this.set_active_view(ActiveView::Agent2Thread { thread_element }, window, cx); }) @@ -1521,10 +1522,7 @@ impl Focusable for AgentPanel { fn focus_handle(&self, cx: &App) -> FocusHandle { match &self.active_view { ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx), - ActiveView::Agent2Thread { .. } => { - // todo! add own message editor to agent2 - cx.focus_handle() - } + ActiveView::Agent2Thread { thread_element, .. } => thread_element.focus_handle(cx), ActiveView::History => self.history.focus_handle(cx), ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx), ActiveView::Configuration => { diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 3f77b2fa70151626e0782e1cdbe8b33f1c2cb40b..62f1eb7bf6294f58cb48a3368b7df05cd0804a6c 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -66,7 +66,6 @@ actions!( OpenHistory, AddContextServer, RemoveSelectedThread, - Chat, ChatWithFollow, CycleNextInlineAssist, CyclePreviousInlineAssist, diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index 39f83d50cb51208521e57e57d1807c8a6371d3d4..a5b7537da0a78e271b5eab3df23cd576576c1632 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -47,13 +47,14 @@ use ui::{ }; use util::ResultExt as _; use workspace::{CollaboratorId, Workspace}; +use zed_actions::agent::Chat; use zed_llm_client::CompletionIntent; use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention}; use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind}; use crate::profile_selector::ProfileSelector; use crate::{ - ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll, + ActiveThread, AgentDiffPane, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll, ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode, ToggleContextPicker, ToggleProfileSelector, register_agent_preview, }; diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index b8c52e27e83ffbdc152e94ea514f30b4c5df8223..8d310653095cdb9104f6e1fe0e545cd543cee372 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -198,7 +198,12 @@ pub mod agent { actions!( agent, - [OpenConfiguration, OpenOnboardingModal, ResetOnboarding] + [ + OpenConfiguration, + OpenOnboardingModal, + ResetOnboarding, + Chat + ] ); }