Make thread context wait on detailed summary + remove "Summarizing context..." (#29564)

This moves summarization task management out of `context_store`. The
code there was draining a Vec of tasks to block on, but this is no
longer a good fit for message_editor's context loading. It needs to be
able to repeatedly await on the thread summarization tasks involved in
the context.

Discussed with Danilo, and he thinks it'd be good to remove the current
"Summarizing context" anyway since it causes layout shift. If message
send is blocked on summarizing, the pulsing context pill is sufficient
for now. This UI change made this overall change more straightforward.

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-04-28 15:21:20 -06:00 committed by GitHub
parent 99df1190a9
commit bbc66748dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 160 additions and 197 deletions

View file

@ -60,11 +60,10 @@ pub struct MessageEditor {
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
model_selector: Entity<AssistantModelSelector>,
last_loaded_context: Option<ContextLoadResult>,
context_load_task: Option<Shared<Task<()>>>,
load_context_task: Option<Shared<Task<()>>>,
profile_selector: Entity<ProfileSelector>,
edits_expanded: bool,
editor_is_expanded: bool,
waiting_for_summaries_to_send: bool,
last_estimated_token_count: Option<usize>,
update_token_count_task: Option<Task<anyhow::Result<()>>>,
_subscriptions: Vec<Subscription>,
@ -149,7 +148,8 @@ impl MessageEditor {
_ => {}
}),
cx.observe(&context_store, |this, _, cx| {
let _ = this.start_context_load(cx);
// When context changes, reload it for token counting.
let _ = this.reload_context(cx);
}),
];
@ -163,7 +163,7 @@ impl MessageEditor {
prompt_store,
context_strip,
context_picker_menu_handle,
context_load_task: None,
load_context_task: None,
last_loaded_context: None,
model_selector: cx.new(|cx| {
AssistantModelSelector::new(
@ -177,7 +177,6 @@ impl MessageEditor {
}),
edits_expanded: false,
editor_is_expanded: false,
waiting_for_summaries_to_send: false,
profile_selector: cx
.new(|cx| ProfileSelector::new(fs, thread_store, editor.focus_handle(cx), cx)),
last_estimated_token_count: None,
@ -289,7 +288,7 @@ impl MessageEditor {
let thread = self.thread.clone();
let git_store = self.project.read(cx).git_store().clone();
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
let context_task = self.load_context(cx);
let context_task = self.reload_context(cx);
let window_handle = window.window_handle();
cx.spawn(async move |_this, cx| {
@ -312,30 +311,6 @@ impl MessageEditor {
.detach();
}
fn wait_for_summaries(&mut self, cx: &mut Context<Self>) -> Task<()> {
let context_store = self.context_store.clone();
cx.spawn(async move |this, cx| {
if let Some(wait_for_summaries) = context_store
.update(cx, |context_store, cx| context_store.wait_for_summaries(cx))
.ok()
{
this.update(cx, |this, cx| {
this.waiting_for_summaries_to_send = true;
cx.notify();
})
.ok();
wait_for_summaries.await;
this.update(cx, |this, cx| {
this.waiting_for_summaries_to_send = false;
cx.notify();
})
.ok();
}
})
}
fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let cancelled = self.thread.update(cx, |thread, cx| {
thread.cancel_last_completion(Some(window.window_handle()), cx)
@ -664,31 +639,31 @@ impl MessageEditor {
})
.when(!is_editor_empty, |parent| {
parent.child(
IconButton::new("send-message", IconName::Send)
.icon_color(Color::Accent)
.style(ButtonStyle::Filled)
.disabled(
!is_model_selected
|| self
.waiting_for_summaries_to_send,
)
.on_click({
let focus_handle = focus_handle.clone();
move |_event, window, cx| {
focus_handle.dispatch_action(
&Chat, window, cx,
);
}
})
.tooltip(move |window, cx| {
Tooltip::for_action(
"Stop and Send New Message",
&Chat,
window,
cx,
IconButton::new(
"send-message",
IconName::Send,
)
}),
)
.icon_color(Color::Accent)
.style(ButtonStyle::Filled)
.disabled(!is_model_selected)
.on_click({
let focus_handle =
focus_handle.clone();
move |_event, window, cx| {
focus_handle.dispatch_action(
&Chat, window, cx,
);
}
})
.tooltip(move |window, cx| {
Tooltip::for_action(
"Stop and Send New Message",
&Chat,
window,
cx,
)
}),
)
})
} else {
parent.child(
@ -696,10 +671,7 @@ impl MessageEditor {
.icon_color(Color::Accent)
.style(ButtonStyle::Filled)
.disabled(
is_editor_empty
|| !is_model_selected
|| self
.waiting_for_summaries_to_send,
is_editor_empty || !is_model_selected,
)
.on_click({
let focus_handle = focus_handle.clone();
@ -1041,16 +1013,8 @@ impl MessageEditor {
self.update_token_count_task.is_some()
}
fn handle_message_changed(&mut self, cx: &mut Context<Self>) {
self.message_or_context_changed(true, cx);
}
fn start_context_load(&mut self, cx: &mut Context<Self>) -> Shared<Task<()>> {
let summaries_task = self.wait_for_summaries(cx);
fn reload_context(&mut self, cx: &mut Context<Self>) -> Task<Option<ContextLoadResult>> {
let load_task = cx.spawn(async move |this, cx| {
// Waits for detailed summaries before `load_context`, as it directly reads these from
// the thread. TODO: Would be cleaner to have context loading await on summarization.
summaries_task.await;
let Ok(load_task) = this.update(cx, |this, cx| {
let new_context = this.context_store.read_with(cx, |context_store, cx| {
context_store.new_context_for_thread(this.thread.read(cx))
@ -1062,27 +1026,26 @@ impl MessageEditor {
let result = load_task.await;
this.update(cx, |this, cx| {
this.last_loaded_context = Some(result);
this.context_load_task = None;
this.load_context_task = None;
this.message_or_context_changed(false, cx);
})
.ok();
});
// Replace existing load task, if any, causing it to be cancelled.
let load_task = load_task.shared();
self.context_load_task = Some(load_task.clone());
load_task
}
fn load_context(&mut self, cx: &mut Context<Self>) -> Task<Option<ContextLoadResult>> {
let context_load_task = self.start_context_load(cx);
self.load_context_task = Some(load_task.clone());
cx.spawn(async move |this, cx| {
context_load_task.await;
load_task.await;
this.read_with(cx, |this, _cx| this.last_loaded_context.clone())
.ok()
.flatten()
})
}
fn handle_message_changed(&mut self, cx: &mut Context<Self>) {
self.message_or_context_changed(true, cx);
}
fn message_or_context_changed(&mut self, debounce: bool, cx: &mut Context<Self>) {
cx.emit(MessageEditorEvent::Changed);
self.update_token_count_task.take();
@ -1183,41 +1146,6 @@ impl Render for MessageEditor {
v_flex()
.size_full()
.when(self.waiting_for_summaries_to_send, |parent| {
parent.child(
h_flex().py_3().w_full().justify_center().child(
h_flex()
.flex_none()
.px_2()
.py_2()
.bg(cx.theme().colors().editor_background)
.border_1()
.border_color(cx.theme().colors().border_variant)
.rounded_lg()
.shadow_md()
.gap_1()
.child(
Icon::new(IconName::ArrowCircle)
.size(IconSize::XSmall)
.color(Color::Muted)
.with_animation(
"arrow-circle",
Animation::new(Duration::from_secs(2)).repeat(),
|icon, delta| {
icon.transform(gpui::Transformation::rotate(
gpui::percentage(delta),
))
},
),
)
.child(
Label::new("Summarizing context…")
.size(LabelSize::XSmall)
.color(Color::Muted),
),
),
)
})
.when(changed_buffers.len() > 0, |parent| {
parent.child(self.render_changed_buffers(&changed_buffers, window, cx))
})