thread view: Scroll to the bottom when sending new messages + adjust controls display (#35586)

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-08-04 12:44:29 -03:00 committed by GitHub
parent bb5af6f76d
commit d577ef52cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -424,11 +424,14 @@ impl AcpThreadView {
let mention_set = self.mention_set.clone(); let mention_set = self.mention_set.clone();
self.set_editor_is_expanded(false, cx); self.set_editor_is_expanded(false, cx);
self.message_editor.update(cx, |editor, cx| { self.message_editor.update(cx, |editor, cx| {
editor.clear(window, cx); editor.clear(window, cx);
editor.remove_creases(mention_set.lock().drain(), cx) editor.remove_creases(mention_set.lock().drain(), cx)
}); });
self.scroll_to_bottom(cx);
self.message_history.borrow_mut().push(chunks); self.message_history.borrow_mut().push(chunks);
} }
@ -2022,15 +2025,15 @@ impl AcpThreadView {
.icon_color(Color::Accent) .icon_color(Color::Accent)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.disabled(self.thread().is_none() || is_editor_empty) .disabled(self.thread().is_none() || is_editor_empty)
.on_click(cx.listener(|this, _, window, cx| {
this.chat(&Chat, window, cx);
}))
.when(!is_editor_empty, |button| { .when(!is_editor_empty, |button| {
button.tooltip(move |window, cx| Tooltip::for_action("Send", &Chat, window, cx)) button.tooltip(move |window, cx| Tooltip::for_action("Send", &Chat, window, cx))
}) })
.when(is_editor_empty, |button| { .when(is_editor_empty, |button| {
button.tooltip(Tooltip::text("Type a message to submit")) button.tooltip(Tooltip::text("Type a message to submit"))
}) })
.on_click(cx.listener(|this, _, window, cx| {
this.chat(&Chat, window, cx);
}))
.into_any_element() .into_any_element()
} else { } else {
IconButton::new("stop-generation", IconName::StopFilled) IconButton::new("stop-generation", IconName::StopFilled)
@ -2245,6 +2248,14 @@ impl AcpThreadView {
cx.notify(); cx.notify();
} }
pub fn scroll_to_bottom(&mut self, cx: &mut Context<Self>) {
if let Some(thread) = self.thread() {
let entry_count = thread.read(cx).entries().len();
self.list_state.reset(entry_count);
cx.notify();
}
}
fn notify_with_sound( fn notify_with_sound(
&mut self, &mut self,
caption: impl Into<SharedString>, caption: impl Into<SharedString>,
@ -2392,17 +2403,9 @@ impl AcpThreadView {
self.notification_subscriptions.remove(&window); self.notification_subscriptions.remove(&window);
} }
} }
}
impl Focusable for AcpThreadView { fn render_thread_controls(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
fn focus_handle(&self, cx: &App) -> FocusHandle { let open_as_markdown = IconButton::new("open-as-markdown", IconName::FileText)
self.message_editor.focus_handle(cx)
}
}
impl Render for AcpThreadView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let open_as_markdown = IconButton::new("open-as-markdown", IconName::DocumentText)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
.icon_color(Color::Ignored) .icon_color(Color::Ignored)
.tooltip(Tooltip::text("Open Thread as Markdown")) .tooltip(Tooltip::text("Open Thread as Markdown"))
@ -2421,6 +2424,28 @@ impl Render for AcpThreadView {
this.scroll_to_top(cx); this.scroll_to_top(cx);
})); }));
h_flex()
.mt_1()
.mr_1()
.py_2()
.px(RESPONSE_PADDING_X)
.opacity(0.4)
.hover(|style| style.opacity(1.))
.flex_wrap()
.justify_end()
.child(open_as_markdown)
.child(scroll_to_top)
}
}
impl Focusable for AcpThreadView {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.message_editor.focus_handle(cx)
}
}
impl Render for AcpThreadView {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex() v_flex()
.size_full() .size_full()
.key_context("AcpThread") .key_context("AcpThread")
@ -2456,42 +2481,39 @@ impl Render for AcpThreadView {
.items_center() .items_center()
.justify_center() .justify_center()
.child(self.render_error_state(e, cx)), .child(self.render_error_state(e, cx)),
ThreadState::Ready { thread, .. } => v_flex().flex_1().map(|this| { ThreadState::Ready { thread, .. } => {
if self.list_state.item_count() > 0 { let thread_clone = thread.clone();
this.child(
list(self.list_state.clone()) v_flex().flex_1().map(|this| {
.with_sizing_behavior(gpui::ListSizingBehavior::Auto) if self.list_state.item_count() > 0 {
.flex_grow() let is_generating =
.into_any(), matches!(thread_clone.read(cx).status(), ThreadStatus::Generating);
)
.child( this.child(
h_flex() list(self.list_state.clone())
.group("controls") .with_sizing_behavior(gpui::ListSizingBehavior::Auto)
.mt_1() .flex_grow()
.mr_1() .into_any(),
.py_2() )
.px(RESPONSE_PADDING_X) .when(!is_generating, |this| {
.opacity(0.4) this.child(self.render_thread_controls(cx))
.hover(|style| style.opacity(1.)) })
.flex_wrap() .children(match thread_clone.read(cx).status() {
.justify_end() ThreadStatus::Idle | ThreadStatus::WaitingForToolConfirmation => {
.child(open_as_markdown) None
.child(scroll_to_top) }
.into_any_element(), ThreadStatus::Generating => div()
) .px_5()
.children(match thread.read(cx).status() { .py_2()
ThreadStatus::Idle | ThreadStatus::WaitingForToolConfirmation => None, .child(LoadingLabel::new("").size(LabelSize::Small))
ThreadStatus::Generating => div() .into(),
.px_5() })
.py_2() .children(self.render_activity_bar(&thread_clone, window, cx))
.child(LoadingLabel::new("").size(LabelSize::Small)) } else {
.into(), this.child(self.render_empty_state(cx))
}) }
.children(self.render_activity_bar(&thread, window, cx)) })
} else { }
this.child(self.render_empty_state(cx))
}
}),
}) })
.when_some(self.last_error.clone(), |el, error| { .when_some(self.last_error.clone(), |el, error| {
el.child( el.child(