diff --git a/app/src/ai/blocklist/block/status_bar.rs b/app/src/ai/blocklist/block/status_bar.rs index edea5172caa..7fd9c260dea 100644 --- a/app/src/ai/blocklist/block/status_bar.rs +++ b/app/src/ai/blocklist/block/status_bar.rs @@ -150,6 +150,27 @@ impl BlocklistAIStatusBar { ) -> Self { let history_model = BlocklistAIHistoryModel::handle(ctx); ctx.subscribe_to_model(&history_model, move |me, _, event, ctx| { + match event { + BlocklistAIHistoryEvent::UpdatedStreamingExchange { + exchange_id, + conversation_id, + .. + } => { + if let Some(model) = me.active_exchange_model.as_ref() { + if model.exchange_id(ctx) == Some(*exchange_id) { + me.on_updated_active_exchange_output(ctx); + } else if model.conversation_id(ctx) == Some(*conversation_id) { + ctx.notify(); + } + } + return; + } + BlocklistAIHistoryEvent::ConversationUsageMetadataUpdated { .. } => { + ctx.notify(); + return; + } + _ => (), + } if event .terminal_surface_id() .is_some_and(|id| id != terminal_view_id) @@ -495,13 +516,7 @@ impl BlocklistAIStatusBar { ) .ok() }) - .map(|model| { - model.on_updated_output( - Box::new(|me, ctx| me.on_updated_active_exchange_output(ctx)), - ctx, - ); - Box::new(model) as Box> - }); + .map(|model| Box::new(model) as Box>); self.is_summarization_cancel_dialog_open = false; self.stop_summarization_timer(); diff --git a/app/src/ai/blocklist/block/status_bar_tests.rs b/app/src/ai/blocklist/block/status_bar_tests.rs index 18d46ce0099..1861dae499c 100644 --- a/app/src/ai/blocklist/block/status_bar_tests.rs +++ b/app/src/ai/blocklist/block/status_bar_tests.rs @@ -1,10 +1,104 @@ +use std::cell::RefCell; +use std::rc::Rc; + use warp_core::features::FeatureFlag; +use warpui::{App, EntityId, SingletonEntity}; use super::{ - ModelInUse, UNNAMED_FALLBACK_MODEL_WARPING_TEXT, WarpingModelInputs, WarpingModelMessage, - warping_model_message, + BlocklistAIStatusBarEvent, ModelInUse, UNNAMED_FALLBACK_MODEL_WARPING_TEXT, WarpingModelInputs, + WarpingModelMessage, warping_model_message, }; use crate::ai::agent::OutputModelInfo; +use crate::ai::agent::conversation::{AIConversation, AIConversationId}; +use crate::ai::blocklist::{BlocklistAIHistoryEvent, BlocklistAIHistoryModel}; +use crate::test_util::ai_agent_tasks::{create_api_task, create_message}; +use crate::test_util::terminal::{add_window_with_terminal, initialize_app_for_terminal_view}; + +#[test] +fn inactive_exchange_updates_do_not_close_summarization_dialog() { + App::test((), |mut app| async move { + let _tips = FeatureFlag::AgentTips.override_enabled(false); + initialize_app_for_terminal_view(&mut app); + let terminal = add_window_with_terminal(&mut app, None); + let terminal_surface_id = terminal.id(); + let status_bar = terminal.read(&app, |terminal, ctx| { + terminal.input().as_ref(ctx).agent_status_bar().clone() + }); + let history = app.read(BlocklistAIHistoryModel::handle); + let conversation = AIConversation::new_restored( + AIConversationId::new(), + vec![create_api_task( + "root", + (0..2) + .map(|index| { + let mut message = create_message(&format!("message-{index}"), "root"); + message.request_id = format!("request-{index}"); + message + }) + .collect(), + )], + None, + ) + .unwrap(); + let conversation_id = conversation.id(); + let task_id = conversation.get_root_task_id().clone(); + let exchange_ids: Vec<_> = conversation + .all_exchanges() + .iter() + .map(|exchange| exchange.id) + .collect(); + assert_eq!(exchange_ids.len(), 2); + history.update(&mut app, |history, ctx| { + history.restore_conversations(terminal_surface_id, vec![conversation], ctx); + }); + for exchange_id in &exchange_ids { + history.update(&mut app, |_, ctx| { + ctx.emit(BlocklistAIHistoryEvent::AppendedExchange { + exchange_id: *exchange_id, + conversation_id, + task_id: task_id.clone(), + terminal_surface_id, + is_hidden: false, + response_stream_id: None, + }); + }); + } + let dialog_events = Rc::new(RefCell::new(Vec::new())); + let observed_dialog_events = dialog_events.clone(); + app.update(|ctx| { + ctx.subscribe_to_view(&status_bar, move |_, event, _| { + if let BlocklistAIStatusBarEvent::SummarizationCancelDialogToggled { is_open } = + event + { + observed_dialog_events.borrow_mut().push(*is_open); + } + }); + }); + status_bar.update(&mut app, |bar, ctx| { + bar.open_summarization_cancel_dialog(ctx); + }); + + history.update(&mut app, |_, ctx| { + ctx.emit(BlocklistAIHistoryEvent::UpdatedStreamingExchange { + exchange_id: exchange_ids[0], + conversation_id, + terminal_surface_id, + is_hidden: false, + }); + }); + assert_eq!(*dialog_events.borrow(), vec![true]); + + history.update(&mut app, |_, ctx| { + ctx.emit(BlocklistAIHistoryEvent::UpdatedStreamingExchange { + exchange_id: exchange_ids[1], + conversation_id, + terminal_surface_id: EntityId::new(), + is_hidden: false, + }); + }); + assert_eq!(*dialog_events.borrow(), vec![true, false]); + }); +} fn named(display_name: &str) -> ModelInUse { ModelInUse {