assistant2: Use composer for editing inline messages (#11222)

This PR updates the assistant to render historical user messages the
same as ones from the assistant.

Double-clicking on a message will open a composer inline for editing.
Pressing `Esc` will cancel the edit.

We don't yet restore the previous state of the message upon canceling.

<img width="401" alt="Screenshot 2024-04-30 at 4 04 01 PM"
src="https://github.com/zed-industries/zed/assets/1486634/5f253fa8-6578-4054-be30-c495e326d700">

<img width="401" alt="Screenshot 2024-04-30 at 4 04 28 PM"
src="https://github.com/zed-industries/zed/assets/1486634/edf25cea-d97e-44d2-8772-3690eac017a4">


Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-04-30 16:12:20 -04:00 committed by GitHub
parent e8ee0131f1
commit f11a7811f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,8 +13,8 @@ use editor::Editor;
use feature_flags::FeatureFlagAppExt as _; use feature_flags::FeatureFlagAppExt as _;
use futures::{future::join_all, StreamExt}; use futures::{future::join_all, StreamExt};
use gpui::{ use gpui::{
list, AnyElement, AppContext, AsyncWindowContext, EventEmitter, FocusHandle, FocusableView, list, AnyElement, AppContext, AsyncWindowContext, ClickEvent, EventEmitter, FocusHandle,
ListAlignment, ListState, Model, Render, Task, View, WeakView, FocusableView, ListAlignment, ListState, Model, Render, Task, View, WeakView,
}; };
use language::{language_settings::SoftWrap, LanguageRegistry}; use language::{language_settings::SoftWrap, LanguageRegistry};
use open_ai::{FunctionContent, ToolCall, ToolCallContent}; use open_ai::{FunctionContent, ToolCall, ToolCallContent};
@ -231,6 +231,7 @@ struct AssistantChat {
user_store: Model<UserStore>, user_store: Model<UserStore>,
next_message_id: MessageId, next_message_id: MessageId,
collapsed_messages: HashMap<MessageId, bool>, collapsed_messages: HashMap<MessageId, bool>,
editing_message_id: Option<MessageId>,
pending_completion: Option<Task<()>>, pending_completion: Option<Task<()>>,
tool_registry: Arc<ToolRegistry>, tool_registry: Arc<ToolRegistry>,
project_index: Option<Model<ProjectIndex>>, project_index: Option<Model<ProjectIndex>>,
@ -270,6 +271,7 @@ impl AssistantChat {
language_registry, language_registry,
project_index, project_index,
next_message_id: MessageId(0), next_message_id: MessageId(0),
editing_message_id: None,
collapsed_messages: HashMap::default(), collapsed_messages: HashMap::default(),
pending_completion: None, pending_completion: None,
tool_registry, tool_registry,
@ -288,6 +290,9 @@ impl AssistantChat {
} }
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) { fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
// If we're currently editing a message, cancel the edit.
self.editing_message_id.take();
if self.pending_completion.take().is_none() { if self.pending_completion.take().is_none() {
cx.propagate(); cx.propagate();
return; return;
@ -564,19 +569,49 @@ impl AssistantChat {
match &self.messages[ix] { match &self.messages[ix] {
ChatMessage::User(UserMessage { id, body }) => div() ChatMessage::User(UserMessage { id, body }) => div()
.id(SharedString::from(format!("message-{}-container", id.0)))
.when(!is_last, |element| element.mb_2()) .when(!is_last, |element| element.mb_2())
.child(crate::ui::ChatMessage::new( .map(|element| {
*id, if self.editing_message_id.as_ref() == Some(id) {
UserOrAssistant::User(self.user_store.read(cx).current_user()), element.child(Composer::new(
Some(body.clone().into_any_element()), cx.view().downgrade(),
self.is_message_collapsed(id), self.model.clone(),
Box::new(cx.listener({ body.clone(),
let id = *id; self.user_store.read(cx).current_user(),
move |assistant_chat, _event, _cx| { self.can_submit(),
assistant_chat.toggle_message_collapsed(id) self.tool_registry.clone(),
} ))
})), } else {
)) element
.on_click(cx.listener({
let id = *id;
move |assistant_chat, event: &ClickEvent, _cx| {
if event.up.click_count == 2 {
assistant_chat.editing_message_id = Some(id);
}
}
}))
.child(crate::ui::ChatMessage::new(
*id,
UserOrAssistant::User(self.user_store.read(cx).current_user()),
Some(
RichText::new(
body.read(cx).text(cx),
&[],
&self.language_registry,
)
.element(ElementId::from(id.0), cx),
),
self.is_message_collapsed(id),
Box::new(cx.listener({
let id = *id;
move |assistant_chat, _event, _cx| {
assistant_chat.toggle_message_collapsed(id)
}
})),
))
}
})
.into_any(), .into_any(),
ChatMessage::Assistant(AssistantMessage { ChatMessage::Assistant(AssistantMessage {
id, id,