diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index eed50f1842..e7e7b6da13 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -120,22 +120,32 @@ pub fn init(cx: &mut App) { .register_action(|workspace, _: &OpenAgentDiff, window, cx| { if let Some(panel) = workspace.panel::(cx) { workspace.focus_panel::(window, cx); - let thread = panel.read(cx).thread.read(cx).thread().clone(); - AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx); + match &panel.read(cx).active_view { + ActiveView::Thread { thread, .. } => { + let thread = thread.read(cx).thread().clone(); + AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx); + } + ActiveView::TextThread { .. } + | ActiveView::History + | ActiveView::Configuration => {} + } } }) .register_action(|workspace, _: &Follow, window, cx| { workspace.follow(CollaboratorId::Agent, window, cx); }) .register_action(|workspace, _: &ExpandMessageEditor, window, cx| { - if let Some(panel) = workspace.panel::(cx) { - workspace.focus_panel::(window, cx); - panel.update(cx, |panel, cx| { - panel.message_editor.update(cx, |editor, cx| { + let Some(panel) = workspace.panel::(cx) else { + return; + }; + workspace.focus_panel::(window, cx); + panel.update(cx, |panel, cx| { + if let Some(message_editor) = panel.active_message_editor() { + message_editor.update(cx, |editor, cx| { editor.expand_message_editor(&ExpandMessageEditor, window, cx); }); - }); - } + } + }); }) .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| { if let Some(panel) = workspace.panel::(cx) { @@ -173,8 +183,9 @@ pub fn init(cx: &mut App) { enum ActiveView { Thread { + thread: Entity, change_title_editor: Entity, - thread: WeakEntity, + message_editor: Entity, _subscriptions: Vec, }, TextThread { @@ -202,8 +213,13 @@ impl ActiveView { } } - pub fn thread(thread: Entity, window: &mut Window, cx: &mut App) -> Self { - let summary = thread.read(cx).summary().or_default(); + pub fn thread( + active_thread: Entity, + message_editor: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let summary = active_thread.read(cx).summary(cx).or_default(); let editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); @@ -212,20 +228,37 @@ impl ActiveView { }); let subscriptions = vec![ + cx.subscribe(&message_editor, |this, _, event, cx| match event { + MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => { + cx.notify(); + } + MessageEditorEvent::ScrollThreadToBottom => match &this.active_view { + ActiveView::Thread { thread, .. } => { + thread.update(cx, |thread, cx| { + thread.scroll_to_bottom(cx); + }); + } + ActiveView::TextThread { .. } + | ActiveView::History + | ActiveView::Configuration => {} + }, + }), window.subscribe(&editor, cx, { { - let thread = thread.clone(); + let thread = active_thread.clone(); move |editor, event, window, cx| match event { EditorEvent::BufferEdited => { let new_summary = editor.read(cx).text(cx); thread.update(cx, |thread, cx| { - thread.set_summary(new_summary, cx); + thread.thread().update(cx, |thread, cx| { + thread.set_summary(new_summary, cx); + }); }) } EditorEvent::Blurred => { if editor.read(cx).text(cx).is_empty() { - let summary = thread.read(cx).summary().or_default(); + let summary = thread.read(cx).summary(cx).or_default(); editor.update(cx, |editor, cx| { editor.set_text(summary, window, cx); @@ -236,9 +269,14 @@ impl ActiveView { } } }), - window.subscribe(&thread, cx, { + cx.subscribe(&active_thread, |_, _, event, cx| match &event { + ActiveThreadEvent::EditingMessageTokenCountChanged => { + cx.notify(); + } + }), + cx.subscribe_in(&active_thread.read(cx).thread().clone(), window, { let editor = editor.clone(); - move |thread, event, window, cx| match event { + move |_, thread, event, window, cx| match event { ThreadEvent::SummaryGenerated => { let summary = thread.read(cx).summary().or_default(); @@ -246,6 +284,9 @@ impl ActiveView { editor.set_text(summary, window, cx); }) } + ThreadEvent::MessageAdded(_) => { + cx.notify(); + } _ => {} } }), @@ -253,7 +294,8 @@ impl ActiveView { Self::Thread { change_title_editor: editor, - thread: thread.downgrade(), + thread: active_thread, + message_editor: message_editor, _subscriptions: subscriptions, } } @@ -366,9 +408,6 @@ pub struct AgentPanel { fs: Arc, language_registry: Arc, thread_store: Entity, - thread: Entity, - message_editor: Entity, - _active_thread_subscriptions: Vec, _default_model_subscription: Subscription, context_store: Entity, prompt_store: Option>, @@ -513,18 +552,6 @@ impl AgentPanel { ) }); - let message_editor_subscription = - cx.subscribe(&message_editor, |this, _, event, cx| match event { - MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => { - cx.notify(); - } - MessageEditorEvent::ScrollThreadToBottom => { - this.thread.update(cx, |thread, cx| { - thread.scroll_to_bottom(cx); - }); - } - }); - let thread_id = thread.read(cx).id().clone(); let history_store = cx.new(|cx| { HistoryStore::new( @@ -537,9 +564,22 @@ impl AgentPanel { cx.observe(&history_store, |_, _, cx| cx.notify()).detach(); + let active_thread = cx.new(|cx| { + ActiveThread::new( + thread.clone(), + thread_store.clone(), + context_store.clone(), + message_editor_context_store.clone(), + language_registry.clone(), + workspace.clone(), + window, + cx, + ) + }); + let panel_type = AgentSettings::get_global(cx).default_view; let active_view = match panel_type { - DefaultView::Thread => ActiveView::thread(thread.clone(), window, cx), + DefaultView::Thread => ActiveView::thread(active_thread, message_editor, window, cx), DefaultView::TextThread => { let context = context_store.update(cx, |context_store, cx| context_store.create(cx)); @@ -567,33 +607,8 @@ impl AgentPanel { } }; - let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| { - if let ThreadEvent::MessageAdded(_) = &event { - // needed to leave empty state - cx.notify(); - } - }); - let active_thread = cx.new(|cx| { - ActiveThread::new( - thread.clone(), - thread_store.clone(), - context_store.clone(), - message_editor_context_store.clone(), - language_registry.clone(), - workspace.clone(), - window, - cx, - ) - }); AgentDiff::set_active_thread(&workspace, &thread, window, cx); - let active_thread_subscription = - cx.subscribe(&active_thread, |_, _, event, cx| match &event { - ActiveThreadEvent::EditingMessageTokenCountChanged => { - cx.notify(); - } - }); - let weak_panel = weak_self.clone(); window.defer(cx, move |window, cx| { @@ -630,13 +645,18 @@ impl AgentPanel { let _default_model_subscription = cx.subscribe( &LanguageModelRegistry::global(cx), |this, _, event: &language_model::Event, cx| match event { - language_model::Event::DefaultModelChanged => { - this.thread - .read(cx) - .thread() - .clone() - .update(cx, |thread, cx| thread.get_or_init_configured_model(cx)); - } + language_model::Event::DefaultModelChanged => match &this.active_view { + ActiveView::Thread { thread, .. } => { + thread + .read(cx) + .thread() + .clone() + .update(cx, |thread, cx| thread.get_or_init_configured_model(cx)); + } + ActiveView::TextThread { .. } + | ActiveView::History + | ActiveView::Configuration => {} + }, _ => {} }, ); @@ -649,13 +669,6 @@ impl AgentPanel { fs: fs.clone(), language_registry, thread_store: thread_store.clone(), - thread: active_thread, - message_editor, - _active_thread_subscriptions: vec![ - thread_subscription, - active_thread_subscription, - message_editor_subscription, - ], _default_model_subscription, context_store, prompt_store, @@ -716,24 +729,34 @@ impl AgentPanel { } fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context) { - self.thread - .update(cx, |thread, cx| thread.cancel_last_completion(window, cx)); + match &self.active_view { + ActiveView::Thread { thread, .. } => { + thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx)); + } + ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {} + } + } + + fn active_message_editor(&self) -> Option<&Entity> { + match &self.active_view { + ActiveView::Thread { message_editor, .. } => Some(message_editor), + ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => None, + } } fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context) { // Preserve chat box text when using creating new thread from summary' - let preserved_text = action - .from_thread_id - .is_some() - .then(|| self.message_editor.read(cx).get_text(cx).trim().to_string()); + let preserved_text = if action.from_thread_id.is_some() { + self.active_message_editor() + .map(|editor| editor.read(cx).get_text(cx).trim().to_string()) + } else { + None + }; let thread = self .thread_store .update(cx, |this, cx| this.create_thread(cx)); - let thread_view = ActiveView::thread(thread.clone(), window, cx); - self.set_active_view(thread_view, window, cx); - let context_store = cx.new(|_cx| { ContextStore::new( self.project.downgrade(), @@ -761,14 +784,7 @@ impl AgentPanel { .detach_and_log_err(cx); } - let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| { - if let ThreadEvent::MessageAdded(_) = &event { - // needed to leave empty state - cx.notify(); - } - }); - - self.thread = cx.new(|cx| { + let active_thread = cx.new(|cx| { ActiveThread::new( thread.clone(), self.thread_store.clone(), @@ -780,55 +796,34 @@ impl AgentPanel { cx, ) }); - AgentDiff::set_active_thread(&self.workspace, &thread, window, cx); - let active_thread_subscription = - cx.subscribe(&self.thread, |_, _, event, cx| match &event { - ActiveThreadEvent::EditingMessageTokenCountChanged => { - cx.notify(); - } - }); - - self.message_editor = cx.new(|cx| { + let message_editor = cx.new(|cx| { MessageEditor::new( self.fs.clone(), self.workspace.clone(), self.user_store.clone(), - context_store, + context_store.clone(), self.prompt_store.clone(), self.thread_store.downgrade(), self.context_store.downgrade(), - thread, + thread.clone(), window, cx, ) }); if let Some(text) = preserved_text { - self.message_editor.update(cx, |editor, cx| { + message_editor.update(cx, |editor, cx| { editor.set_text(text, window, cx); }); } - self.message_editor.focus_handle(cx).focus(window); + message_editor.focus_handle(cx).focus(window); - let message_editor_subscription = - cx.subscribe(&self.message_editor, |this, _, event, cx| match event { - MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => { - cx.notify(); - } - MessageEditorEvent::ScrollThreadToBottom => { - this.thread.update(cx, |thread, cx| { - thread.scroll_to_bottom(cx); - }); - } - }); + let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx); + self.set_active_view(thread_view, window, cx); - self._active_thread_subscriptions = vec![ - thread_subscription, - active_thread_subscription, - message_editor_subscription, - ]; + AgentDiff::set_active_thread(&self.workspace, &thread, window, cx); } fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context) { @@ -980,22 +975,14 @@ impl AgentPanel { window: &mut Window, cx: &mut Context, ) { - let thread_view = ActiveView::thread(thread.clone(), window, cx); - self.set_active_view(thread_view, window, cx); let context_store = cx.new(|_cx| { ContextStore::new( self.project.downgrade(), Some(self.thread_store.downgrade()), ) }); - let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| { - if let ThreadEvent::MessageAdded(_) = &event { - // needed to leave empty state - cx.notify(); - } - }); - self.thread = cx.new(|cx| { + let active_thread = cx.new(|cx| { ActiveThread::new( thread.clone(), self.thread_store.clone(), @@ -1007,16 +994,7 @@ impl AgentPanel { cx, ) }); - AgentDiff::set_active_thread(&self.workspace, &thread, window, cx); - - let active_thread_subscription = - cx.subscribe(&self.thread, |_, _, event, cx| match &event { - ActiveThreadEvent::EditingMessageTokenCountChanged => { - cx.notify(); - } - }); - - self.message_editor = cx.new(|cx| { + let message_editor = cx.new(|cx| { MessageEditor::new( self.fs.clone(), self.workspace.clone(), @@ -1025,30 +1003,16 @@ impl AgentPanel { self.prompt_store.clone(), self.thread_store.downgrade(), self.context_store.downgrade(), - thread, + thread.clone(), window, cx, ) }); - self.message_editor.focus_handle(cx).focus(window); + message_editor.focus_handle(cx).focus(window); - let message_editor_subscription = - cx.subscribe(&self.message_editor, |this, _, event, cx| match event { - MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => { - cx.notify(); - } - MessageEditorEvent::ScrollThreadToBottom => { - this.thread.update(cx, |thread, cx| { - thread.scroll_to_bottom(cx); - }); - } - }); - - self._active_thread_subscriptions = vec![ - thread_subscription, - active_thread_subscription, - message_editor_subscription, - ]; + let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx); + self.set_active_view(thread_view, window, cx); + AgentDiff::set_active_thread(&self.workspace, &thread, window, cx); } pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context) { @@ -1058,18 +1022,14 @@ impl AgentPanel { self.active_view = previous_view; match &self.active_view { - ActiveView::Thread { .. } => { - self.message_editor.focus_handle(cx).focus(window); + ActiveView::Thread { message_editor, .. } => { + message_editor.focus_handle(cx).focus(window); } ActiveView::TextThread { context_editor, .. } => { context_editor.focus_handle(cx).focus(window); } - _ => {} + ActiveView::History | ActiveView::Configuration => {} } - } else { - self.active_view = - ActiveView::thread(self.thread.read(cx).thread().clone(), window, cx); - self.message_editor.focus_handle(cx).focus(window); } cx.notify(); } @@ -1175,12 +1135,17 @@ impl AgentPanel { window: &mut Window, cx: &mut Context, ) { - let thread = self.thread.read(cx).thread().clone(); - self.workspace - .update(cx, |workspace, cx| { - AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx) - }) - .log_err(); + match &self.active_view { + ActiveView::Thread { thread, .. } => { + let thread = thread.read(cx).thread().clone(); + self.workspace + .update(cx, |workspace, cx| { + AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx) + }) + .log_err(); + } + ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {} + } } pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context) { @@ -1222,12 +1187,18 @@ impl AgentPanel { return; }; - let Some(thread) = self.active_thread() else { - return; - }; - - active_thread::open_active_thread_as_markdown(thread, workspace, window, cx) - .detach_and_log_err(cx); + match &self.active_view { + ActiveView::Thread { thread, .. } => { + active_thread::open_active_thread_as_markdown( + thread.read(cx).thread().clone(), + workspace, + window, + cx, + ) + .detach_and_log_err(cx); + } + ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {} + } } fn handle_agent_configuration_event( @@ -1257,9 +1228,9 @@ impl AgentPanel { } } - pub(crate) fn active_thread(&self) -> Option> { + pub(crate) fn active_thread(&self, cx: &App) -> Option> { match &self.active_view { - ActiveView::Thread { thread, .. } => thread.upgrade(), + ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()), _ => None, } } @@ -1273,19 +1244,19 @@ impl AgentPanel { .update(cx, |this, cx| this.delete_thread(thread_id, cx)) } - pub(crate) fn has_active_thread(&self) -> bool { - matches!(self.active_view, ActiveView::Thread { .. }) - } - fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context) { - let thread_state = self.thread.read(cx).thread().read(cx); + let ActiveView::Thread { thread, .. } = &self.active_view else { + return; + }; + + let thread_state = thread.read(cx).thread().read(cx); if !thread_state.tool_use_limit_reached() { return; } let model = thread_state.configured_model().map(|cm| cm.model.clone()); if let Some(model) = model { - self.thread.update(cx, |active_thread, cx| { + thread.update(cx, |active_thread, cx| { active_thread.thread().update(cx, |thread, cx| { thread.insert_invisible_continue_message(cx); thread.advance_prompt_id(); @@ -1308,7 +1279,11 @@ impl AgentPanel { _window: &mut Window, cx: &mut Context, ) { - self.thread.update(cx, |active_thread, cx| { + let ActiveView::Thread { thread, .. } = &self.active_view else { + return; + }; + + thread.update(cx, |active_thread, cx| { active_thread.thread().update(cx, |thread, _cx| { let current_mode = thread.completion_mode(); @@ -1353,13 +1328,12 @@ impl AgentPanel { match &self.active_view { ActiveView::Thread { thread, .. } => { - if let Some(thread) = thread.upgrade() { - if thread.read(cx).is_empty() { - let id = thread.read(cx).id().clone(); - self.history_store.update(cx, |store, cx| { - store.remove_recently_opened_thread(id, cx); - }); - } + let thread = thread.read(cx); + if thread.is_empty() { + let id = thread.thread().read(cx).id().clone(); + self.history_store.update(cx, |store, cx| { + store.remove_recently_opened_thread(id, cx); + }); } } _ => {} @@ -1367,10 +1341,8 @@ impl AgentPanel { match &new_view { ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| { - if let Some(thread) = thread.upgrade() { - let id = thread.read(cx).id().clone(); - store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx); - } + let id = thread.read(cx).thread().read(cx).id().clone(); + store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx); }), ActiveView::TextThread { context_editor, .. } => { self.history_store.update(cx, |store, cx| { @@ -1464,7 +1436,7 @@ impl AgentPanel { impl Focusable for AgentPanel { fn focus_handle(&self, cx: &App) -> FocusHandle { match &self.active_view { - ActiveView::Thread { .. } => self.message_editor.focus_handle(cx), + ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx), ActiveView::History => self.history.focus_handle(cx), ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx), ActiveView::Configuration => { @@ -1573,14 +1545,17 @@ impl AgentPanel { let content = match &self.active_view { ActiveView::Thread { + thread: active_thread, change_title_editor, .. } => { - let active_thread = self.thread.read(cx); - let state = if active_thread.is_empty() { - &ThreadSummary::Pending - } else { - active_thread.summary(cx) + let state = { + let active_thread = active_thread.read(cx); + if active_thread.is_empty() { + &ThreadSummary::Pending + } else { + active_thread.summary(cx) + } }; match state { @@ -1600,7 +1575,7 @@ impl AgentPanel { .child( ui::IconButton::new("retry-summary-generation", IconName::RotateCcw) .on_click({ - let active_thread = self.thread.clone(); + let active_thread = active_thread.clone(); move |_, _window, cx| { active_thread.update(cx, |thread, cx| { thread.regenerate_summary(cx); @@ -1681,22 +1656,11 @@ impl AgentPanel { } fn render_toolbar(&self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let active_thread = self.thread.read(cx); let user_store = self.user_store.read(cx); - let thread = active_thread.thread().read(cx); - let thread_id = thread.id().clone(); - let is_empty = active_thread.is_empty(); - let editor_empty = self.message_editor.read(cx).is_editor_fully_empty(cx); let usage = user_store.model_request_usage(); let account_url = zed_urls::account_url(cx); - let show_token_count = match &self.active_view { - ActiveView::Thread { .. } => !is_empty || !editor_empty, - ActiveView::TextThread { .. } => true, - _ => false, - }; - let focus_handle = self.focus_handle(cx); let go_back_button = div().child( @@ -1761,6 +1725,11 @@ impl AgentPanel { "Zoom In" }; + let active_thread = match &self.active_view { + ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()), + ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => None, + }; + let agent_extra_menu = PopoverMenu::new("agent-options-menu") .trigger_with_tooltip( IconButton::new("agent-options-menu", IconName::Ellipsis) @@ -1781,17 +1750,23 @@ impl AgentPanel { .anchor(Corner::TopRight) .with_handle(self.assistant_dropdown_menu_handle.clone()) .menu(move |window, cx| { - Some(ContextMenu::build(window, cx, |mut menu, _window, _cx| { + let active_thread = active_thread.clone(); + Some(ContextMenu::build(window, cx, |mut menu, _window, cx| { menu = menu .action("New Thread", NewThread::default().boxed_clone()) .action("New Text Thread", NewTextThread.boxed_clone()) - .when(!is_empty, |menu| { - menu.action( - "New From Summary", - Box::new(NewThread { - from_thread_id: Some(thread_id.clone()), - }), - ) + .when_some(active_thread, |this, active_thread| { + let thread = active_thread.read(cx); + if !thread.is_empty() { + this.action( + "New From Summary", + Box::new(NewThread { + from_thread_id: Some(thread.id().clone()), + }), + ) + } else { + this + } }) .separator(); @@ -1878,9 +1853,7 @@ impl AgentPanel { h_flex() .h_full() .gap_2() - .when(show_token_count, |parent| { - parent.children(self.render_token_count(&thread, cx)) - }) + .children(self.render_token_count(cx)) .child( h_flex() .h_full() @@ -1913,26 +1886,41 @@ impl AgentPanel { ) } - fn render_token_count(&self, thread: &Thread, cx: &App) -> Option { - let is_generating = thread.is_generating(); - let message_editor = self.message_editor.read(cx); + fn render_token_count(&self, cx: &App) -> Option { + let (active_thread, message_editor) = match &self.active_view { + ActiveView::Thread { + thread, + message_editor, + .. + } => (thread.read(cx), message_editor.read(cx)), + ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => { + return None; + } + }; + let editor_empty = message_editor.is_editor_fully_empty(cx); + + if active_thread.is_empty() && editor_empty { + return None; + } + + let thread = active_thread.thread().read(cx); + let is_generating = thread.is_generating(); let conversation_token_usage = thread.total_token_usage()?; - let (total_token_usage, is_estimating) = if let Some((editing_message_id, unsent_tokens)) = - self.thread.read(cx).editing_message_id() - { - let combined = thread - .token_usage_up_to_message(editing_message_id) - .add(unsent_tokens); + let (total_token_usage, is_estimating) = + if let Some((editing_message_id, unsent_tokens)) = active_thread.editing_message_id() { + let combined = thread + .token_usage_up_to_message(editing_message_id) + .add(unsent_tokens); - (combined, unsent_tokens > 0) - } else { - let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0); - let combined = conversation_token_usage.add(unsent_tokens); + (combined, unsent_tokens > 0) + } else { + let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0); + let combined = conversation_token_usage.add(unsent_tokens); - (combined, unsent_tokens > 0) - }; + (combined, unsent_tokens > 0) + }; let is_waiting_to_update_token_count = message_editor.is_waiting_to_update_token_count(); @@ -2030,27 +2018,30 @@ impl AgentPanel { } fn should_render_upsell(&self, cx: &mut Context) -> bool { - if !matches!(self.active_view, ActiveView::Thread { .. }) { - return false; - } + match &self.active_view { + ActiveView::Thread { thread, .. } => { + let is_using_zed_provider = thread + .read(cx) + .thread() + .read(cx) + .configured_model() + .map_or(false, |model| { + model.provider.id().0 == ZED_CLOUD_PROVIDER_ID + }); + + if !is_using_zed_provider { + return false; + } + } + ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => { + return false; + } + }; if self.hide_upsell || Upsell::dismissed() { return false; } - let is_using_zed_provider = self - .thread - .read(cx) - .thread() - .read(cx) - .configured_model() - .map_or(false, |model| { - model.provider.id().0 == ZED_CLOUD_PROVIDER_ID - }); - if !is_using_zed_provider { - return false; - } - let plan = self.user_store.read(cx).current_plan(); if matches!(plan, Some(Plan::ZedPro | Plan::ZedProTrial)) { return false; @@ -2352,20 +2343,6 @@ impl AgentPanel { ) } - fn render_active_thread_or_empty_state( - &self, - window: &mut Window, - cx: &mut Context, - ) -> AnyElement { - if self.thread.read(cx).is_empty() { - return self - .render_thread_empty_state(window, cx) - .into_any_element(); - } - - self.thread.clone().into_any_element() - } - fn render_thread_empty_state( &self, window: &mut Window, @@ -2638,23 +2615,21 @@ impl AgentPanel { window: &mut Window, cx: &mut Context, ) -> Option { - let tool_use_limit_reached = self - .thread - .read(cx) - .thread() - .read(cx) - .tool_use_limit_reached(); + let active_thread = match &self.active_view { + ActiveView::Thread { thread, .. } => thread, + ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => { + return None; + } + }; + + let thread = active_thread.read(cx).thread().read(cx); + + let tool_use_limit_reached = thread.tool_use_limit_reached(); if !tool_use_limit_reached { return None; } - let model = self - .thread - .read(cx) - .thread() - .read(cx) - .configured_model()? - .model; + let model = thread.configured_model()?.model; let focus_handle = self.focus_handle(cx); @@ -2698,14 +2673,17 @@ impl AgentPanel { .map(|kb| kb.size(rems_from_px(10.))), ) .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use.")) - .on_click(cx.listener(|this, _, window, cx| { - this.thread.update(cx, |active_thread, cx| { - active_thread.thread().update(cx, |thread, _cx| { - thread.set_completion_mode(CompletionMode::Burn); + .on_click({ + let active_thread = active_thread.clone(); + cx.listener(move |this, _, window, cx| { + active_thread.update(cx, |active_thread, cx| { + active_thread.thread().update(cx, |thread, _cx| { + thread.set_completion_mode(CompletionMode::Burn); + }); }); - }); - this.continue_conversation(window, cx); - })), + this.continue_conversation(window, cx); + }) + }), ) }), ); @@ -2713,33 +2691,11 @@ impl AgentPanel { Some(div().px_2().pb_2().child(banner).into_any_element()) } - fn render_last_error(&self, cx: &mut Context) -> Option { - let last_error = self.thread.read(cx).last_error()?; - - Some( - div() - .absolute() - .right_3() - .bottom_12() - .max_w_96() - .py_2() - .px_3() - .elevation_2(cx) - .occlude() - .child(match last_error { - ThreadError::PaymentRequired => self.render_payment_required_error(cx), - ThreadError::ModelRequestLimitReached { plan } => { - self.render_model_request_limit_reached_error(plan, cx) - } - ThreadError::Message { header, message } => { - self.render_error_message(header, message, cx) - } - }) - .into_any(), - ) - } - - fn render_payment_required_error(&self, cx: &mut Context) -> AnyElement { + fn render_payment_required_error( + &self, + thread: &Entity, + cx: &mut Context, + ) -> AnyElement { const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used."; v_flex() @@ -2764,25 +2720,27 @@ impl AgentPanel { .mt_1() .gap_1() .child(self.create_copy_button(ERROR_MESSAGE)) - .child(Button::new("subscribe", "Subscribe").on_click(cx.listener( - |this, _, _, cx| { - this.thread.update(cx, |this, _cx| { + .child(Button::new("subscribe", "Subscribe").on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { this.clear_last_error(); }); cx.open_url(&zed_urls::account_url(cx)); cx.notify(); - }, - ))) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( - |this, _, _, cx| { - this.thread.update(cx, |this, _cx| { + } + }))) + .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { this.clear_last_error(); }); cx.notify(); - }, - ))), + } + }))), ) .into_any() } @@ -2790,6 +2748,7 @@ impl AgentPanel { fn render_model_request_limit_reached_error( &self, plan: Plan, + thread: &Entity, cx: &mut Context, ) -> AnyElement { let error_message = match plan { @@ -2830,26 +2789,28 @@ impl AgentPanel { .gap_1() .child(self.create_copy_button(error_message)) .child( - Button::new("subscribe", call_to_action).on_click(cx.listener( - |this, _, _, cx| { - this.thread.update(cx, |this, _cx| { + Button::new("subscribe", call_to_action).on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { this.clear_last_error(); }); cx.open_url(&zed_urls::account_url(cx)); cx.notify(); - }, - )), + } + })), ) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( - |this, _, _, cx| { - this.thread.update(cx, |this, _cx| { + .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { this.clear_last_error(); }); cx.notify(); - }, - ))), + } + }))), ) .into_any() } @@ -2858,6 +2819,7 @@ impl AgentPanel { &self, header: SharedString, message: SharedString, + thread: &Entity, cx: &mut Context, ) -> AnyElement { let message_with_header = format!("{}\n{}", header, message); @@ -2883,15 +2845,16 @@ impl AgentPanel { .mt_1() .gap_1() .child(self.create_copy_button(message_with_header)) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( - |this, _, _, cx| { - this.thread.update(cx, |this, _cx| { + .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { this.clear_last_error(); }); cx.notify(); - }, - ))), + } + }))), ) .into_any() } @@ -3003,8 +2966,8 @@ impl AgentPanel { cx: &mut Context, ) { match &self.active_view { - ActiveView::Thread { .. } => { - let context_store = self.thread.read(cx).context_store().clone(); + ActiveView::Thread { thread, .. } => { + let context_store = thread.read(cx).context_store().clone(); context_store.update(cx, move |context_store, cx| { let mut tasks = Vec::new(); for project_path in &paths { @@ -3096,24 +3059,63 @@ impl Render for AgentPanel { this.continue_conversation(window, cx); })) .on_action(cx.listener(|this, _: &ContinueWithBurnMode, window, cx| { - this.thread.update(cx, |active_thread, cx| { - active_thread.thread().update(cx, |thread, _cx| { - thread.set_completion_mode(CompletionMode::Burn); - }); - }); - this.continue_conversation(window, cx); + match &this.active_view { + ActiveView::Thread { thread, .. } => { + thread.update(cx, |active_thread, cx| { + active_thread.thread().update(cx, |thread, _cx| { + thread.set_completion_mode(CompletionMode::Burn); + }); + }); + this.continue_conversation(window, cx); + } + ActiveView::TextThread { .. } + | ActiveView::History + | ActiveView::Configuration => {} + } })) .on_action(cx.listener(Self::toggle_burn_mode)) .child(self.render_toolbar(window, cx)) .children(self.render_upsell(window, cx)) .children(self.render_trial_end_upsell(window, cx)) .map(|parent| match &self.active_view { - ActiveView::Thread { .. } => parent + ActiveView::Thread { + thread, + message_editor, + .. + } => parent .relative() - .child(self.render_active_thread_or_empty_state(window, cx)) + .child(if thread.read(cx).is_empty() { + self.render_thread_empty_state(window, cx) + .into_any_element() + } else { + thread.clone().into_any_element() + }) .children(self.render_tool_use_limit_reached(window, cx)) - .child(h_flex().child(self.message_editor.clone())) - .children(self.render_last_error(cx)) + .child(h_flex().child(message_editor.clone())) + .when_some(thread.read(cx).last_error(), |this, last_error| { + this.child( + div() + .absolute() + .right_3() + .bottom_12() + .max_w_96() + .py_2() + .px_3() + .elevation_2(cx) + .occlude() + .child(match last_error { + ThreadError::PaymentRequired => { + self.render_payment_required_error(thread, cx) + } + ThreadError::ModelRequestLimitReached { plan } => self + .render_model_request_limit_reached_error(plan, thread, cx), + ThreadError::Message { header, message } => { + self.render_error_message(header, message, thread, cx) + } + }) + .into_any(), + ) + }) .child(self.render_drag_target(cx)), ActiveView::History => parent.child(self.history.clone()), ActiveView::TextThread { @@ -3255,8 +3257,8 @@ impl AgentPanelDelegate for ConcreteAssistantPanelDelegate { // Wait to create a new context until the workspace is no longer // being updated. cx.defer_in(window, move |panel, window, cx| { - if panel.has_active_thread() { - panel.message_editor.update(cx, |message_editor, cx| { + if let Some(message_editor) = panel.active_message_editor() { + message_editor.update(cx, |message_editor, cx| { message_editor.context_store().update(cx, |store, cx| { let buffer = buffer.read(cx); let selection_ranges = selection_ranges diff --git a/crates/agent_ui/src/context_picker.rs b/crates/agent_ui/src/context_picker.rs index 9136307517..b0069a2446 100644 --- a/crates/agent_ui/src/context_picker.rs +++ b/crates/agent_ui/src/context_picker.rs @@ -661,7 +661,7 @@ fn recent_context_picker_entries( let active_thread_id = workspace .panel::(cx) - .and_then(|panel| Some(panel.read(cx).active_thread()?.read(cx).id())); + .and_then(|panel| Some(panel.read(cx).active_thread(cx)?.read(cx).id())); if let Some((thread_store, text_thread_store)) = thread_store .and_then(|store| store.upgrade()) diff --git a/crates/agent_ui/src/context_strip.rs b/crates/agent_ui/src/context_strip.rs index b3890613dc..080ffd2ea0 100644 --- a/crates/agent_ui/src/context_strip.rs +++ b/crates/agent_ui/src/context_strip.rs @@ -161,7 +161,7 @@ impl ContextStrip { let workspace = self.workspace.upgrade()?; let panel = workspace.read(cx).panel::(cx)?.read(cx); - if let Some(active_thread) = panel.active_thread() { + if let Some(active_thread) = panel.active_thread(cx) { let weak_active_thread = active_thread.downgrade(); let active_thread = active_thread.read(cx);