Detailed changes
@@ -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]]
@@ -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
@@ -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<Thread>,
+ // todo! use full message editor from agent2
+ message_editor: Entity<Editor>,
+ send_task: Option<Task<Result<()>>>,
}
impl ThreadElement {
- pub fn new(thread: Entity<Thread>) -> Self {
- Self { thread }
+ pub fn new(thread: Entity<Thread>, window: &mut Window, cx: &mut Context<Self>) -> 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<Self>) {
- // todo!
+ pub fn cancel(&mut self) {
+ self.send_task.take();
+ }
+
+ fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
+ 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<Self>) -> impl IntoElement {
- div().child("agent 2")
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> 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"))
+ }),
+ ),
+ )
}
}
@@ -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 => {
@@ -66,7 +66,6 @@ actions!(
OpenHistory,
AddContextServer,
RemoveSelectedThread,
- Chat,
ChatWithFollow,
CycleNextInlineAssist,
CyclePreviousInlineAssist,
@@ -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,
};
@@ -198,7 +198,12 @@ pub mod agent {
actions!(
agent,
- [OpenConfiguration, OpenOnboardingModal, ResetOnboarding]
+ [
+ OpenConfiguration,
+ OpenOnboardingModal,
+ ResetOnboarding,
+ Chat
+ ]
);
}