assistant2: Refine editing of previous messages (#11233)
This PR refines the UX of editing a previous message, including the following: - Focus the prev message body editor on double-click - Restore previous body text on cancel - Cancel pending completion upon submission of previous message - Drive-by: Remove min height on composer editor Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <marshall@zed.dev>
This commit is contained in:
parent
b18ca1585e
commit
d1abbb1429
2 changed files with 47 additions and 24 deletions
|
@ -231,12 +231,18 @@ pub 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>,
|
editing_message: Option<EditingMessage>,
|
||||||
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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct EditingMessage {
|
||||||
|
id: MessageId,
|
||||||
|
old_body: Arc<str>,
|
||||||
|
body: View<Editor>,
|
||||||
|
}
|
||||||
|
|
||||||
impl AssistantChat {
|
impl AssistantChat {
|
||||||
fn new(
|
fn new(
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
|
@ -271,13 +277,17 @@ impl AssistantChat {
|
||||||
language_registry,
|
language_registry,
|
||||||
project_index,
|
project_index,
|
||||||
next_message_id: MessageId(0),
|
next_message_id: MessageId(0),
|
||||||
editing_message_id: None,
|
editing_message: None,
|
||||||
collapsed_messages: HashMap::default(),
|
collapsed_messages: HashMap::default(),
|
||||||
pending_completion: None,
|
pending_completion: None,
|
||||||
tool_registry,
|
tool_registry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn editing_message_id(&self) -> Option<MessageId> {
|
||||||
|
self.editing_message.as_ref().map(|message| message.id)
|
||||||
|
}
|
||||||
|
|
||||||
fn focused_message_id(&self, cx: &WindowContext) -> Option<MessageId> {
|
fn focused_message_id(&self, cx: &WindowContext) -> Option<MessageId> {
|
||||||
self.messages.iter().find_map(|message| match message {
|
self.messages.iter().find_map(|message| match message {
|
||||||
ChatMessage::User(message) => message
|
ChatMessage::User(message) => message
|
||||||
|
@ -291,30 +301,40 @@ 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.
|
// If we're currently editing a message, cancel the edit.
|
||||||
self.editing_message_id.take();
|
if let Some(editing_message) = self.editing_message.take() {
|
||||||
|
editing_message
|
||||||
if self.pending_completion.take().is_none() {
|
.body
|
||||||
cx.propagate();
|
.update(cx, |body, cx| body.set_text(editing_message.old_body, cx));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ChatMessage::Assistant(message)) = self.messages.last() {
|
if self.pending_completion.take().is_some() {
|
||||||
if message.body.text.is_empty() {
|
if let Some(ChatMessage::Assistant(message)) = self.messages.last() {
|
||||||
self.pop_message(cx);
|
if message.body.text.is_empty() {
|
||||||
|
self.pop_message(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.propagate();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext<Self>) {
|
fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext<Self>) {
|
||||||
// Don't allow multiple concurrent completions.
|
|
||||||
if self.pending_completion.is_some() {
|
|
||||||
cx.propagate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(focused_message_id) = self.focused_message_id(cx) {
|
if let Some(focused_message_id) = self.focused_message_id(cx) {
|
||||||
self.truncate_messages(focused_message_id, cx);
|
self.truncate_messages(focused_message_id, cx);
|
||||||
|
self.pending_completion.take();
|
||||||
|
self.composer_editor.focus_handle(cx).focus(cx);
|
||||||
|
if self.editing_message_id() == Some(focused_message_id) {
|
||||||
|
self.editing_message.take();
|
||||||
|
}
|
||||||
} else if self.composer_editor.focus_handle(cx).is_focused(cx) {
|
} else if self.composer_editor.focus_handle(cx).is_focused(cx) {
|
||||||
|
// Don't allow multiple concurrent completions.
|
||||||
|
if self.pending_completion.is_some() {
|
||||||
|
cx.propagate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let message = self.composer_editor.update(cx, |composer_editor, cx| {
|
let message = self.composer_editor.update(cx, |composer_editor, cx| {
|
||||||
let text = composer_editor.text(cx);
|
let text = composer_editor.text(cx);
|
||||||
let id = self.next_message_id.post_inc();
|
let id = self.next_message_id.post_inc();
|
||||||
|
@ -344,9 +364,7 @@ impl AssistantChat {
|
||||||
.await
|
.await
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, _cx| {
|
||||||
let composer_focus_handle = this.composer_editor.focus_handle(cx);
|
|
||||||
cx.focus(&composer_focus_handle);
|
|
||||||
this.pending_completion = None;
|
this.pending_completion = None;
|
||||||
})
|
})
|
||||||
.context("Failed to push new user message")
|
.context("Failed to push new user message")
|
||||||
|
@ -572,11 +590,11 @@ impl AssistantChat {
|
||||||
.id(SharedString::from(format!("message-{}-container", id.0)))
|
.id(SharedString::from(format!("message-{}-container", id.0)))
|
||||||
.when(!is_last, |element| element.mb_2())
|
.when(!is_last, |element| element.mb_2())
|
||||||
.map(|element| {
|
.map(|element| {
|
||||||
if self.editing_message_id.as_ref() == Some(id) {
|
if self.editing_message_id() == Some(*id) {
|
||||||
element.child(Composer::new(
|
element.child(Composer::new(
|
||||||
body.clone(),
|
body.clone(),
|
||||||
self.user_store.read(cx).current_user(),
|
self.user_store.read(cx).current_user(),
|
||||||
self.can_submit(),
|
true,
|
||||||
self.tool_registry.clone(),
|
self.tool_registry.clone(),
|
||||||
crate::ui::ModelSelector::new(
|
crate::ui::ModelSelector::new(
|
||||||
cx.view().downgrade(),
|
cx.view().downgrade(),
|
||||||
|
@ -588,9 +606,15 @@ impl AssistantChat {
|
||||||
element
|
element
|
||||||
.on_click(cx.listener({
|
.on_click(cx.listener({
|
||||||
let id = *id;
|
let id = *id;
|
||||||
move |assistant_chat, event: &ClickEvent, _cx| {
|
let body = body.clone();
|
||||||
|
move |assistant_chat, event: &ClickEvent, cx| {
|
||||||
if event.up.click_count == 2 {
|
if event.up.click_count == 2 {
|
||||||
assistant_chat.editing_message_id = Some(id);
|
assistant_chat.editing_message = Some(EditingMessage {
|
||||||
|
id,
|
||||||
|
body: body.clone(),
|
||||||
|
old_body: body.read(cx).text(cx).into(),
|
||||||
|
});
|
||||||
|
body.focus_handle(cx).focus(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -69,8 +69,7 @@ impl RenderOnce for Composer {
|
||||||
v_flex()
|
v_flex()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_1()
|
.gap_2()
|
||||||
.min_h(line_height * 4 + px(74.0))
|
|
||||||
.child({
|
.child({
|
||||||
let settings = ThemeSettings::get_global(cx);
|
let settings = ThemeSettings::get_global(cx);
|
||||||
let text_style = TextStyle {
|
let text_style = TextStyle {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue