diff --git a/app/src/ai/blocklist/block.rs b/app/src/ai/blocklist/block.rs index a09a40b1d8..37fdaab1ec 100644 --- a/app/src/ai/blocklist/block.rs +++ b/app/src/ai/blocklist/block.rs @@ -1120,96 +1120,52 @@ impl AIBlock { }) .unwrap_or((None, None)); - let font_settings_handle = FontSettings::handle(ctx); - ctx.subscribe_to_model(&font_settings_handle, |_, _, _, ctx| { - ctx.notify(); + let detected_links_state = DetectedLinksState::default(); + let secret_redaction_state = SecretRedactionState::default(); + + ctx.subscribe_to_model(&find_model, |me, _, event, ctx| match event { + FindEvent::UpdatedFocusedMatch => { + me.handle_find_match_focus_change(ctx); + ctx.notify(); + } + FindEvent::RanFind => ctx.notify(), }); ctx.subscribe_to_model( - &AISettings::handle(ctx), - move |me, settings_model, event, ctx| match event { - AISettingsChangedEvent::AgentModeExecuteReadonlyCommands { .. } => { - if let AutonomySettingSpeedbump::ShouldShowForAutoexecutingReadonlyCommands { - checked, - .. - } = &mut me.autonomy_setting_speedbump - { - *checked = *settings_model - .as_ref(ctx) - .agent_mode_execute_read_only_commands; - } else { - me.autonomy_setting_speedbump = AutonomySettingSpeedbump::None; - } - ctx.notify(); - } - AISettingsChangedEvent::AgentModeCodingPermissions { .. } => { - match &mut me.autonomy_setting_speedbump { - AutonomySettingSpeedbump::ShouldShowForFileAccess { checked, .. } => { - *checked = matches!( - *settings_model.as_ref(ctx).agent_mode_coding_permissions, - AgentModeCodingPermissionsType::AlwaysAllowReading - ); - } - AutonomySettingSpeedbump::ShouldShowForCodebaseSearchFileAccess { - selected_option, - .. - } => { - *selected_option = - match *settings_model.as_ref(ctx).agent_mode_coding_permissions { - AgentModeCodingPermissionsType::AlwaysAllowReading => Some(0), - AgentModeCodingPermissionsType::AllowReadingSpecificFiles => { - Some(1) - } - AgentModeCodingPermissionsType::AlwaysAskBeforeReading => None, - }; - } - _ => {} - } - ctx.notify(); - } - AISettingsChangedEvent::ThinkingDisplayMode { .. } - | AISettingsChangedEvent::OrchestrationMessageDisplayMode { .. } => { - ctx.notify(); - } - _ => {} + &AgentConversationsModel::handle(ctx), + |me, _, event, ctx| { + me.handle_agent_conversations_model_event(event, ctx); }, ); + // Note: UpdatedStreamingExchange is handled by the dedicated on_updated_output() + // callback in model_impl.rs, so we don't need to respond to it here. ctx.subscribe_to_model( - &AgentConversationsModel::handle(ctx), + &BlocklistAIHistoryModel::handle(ctx), |me, _, event, ctx| { - me.handle_agent_conversations_model_event(event, ctx); + if event + .terminal_view_id() + .is_none_or(|id| id == me.terminal_view_id) + { + match event { + BlocklistAIHistoryEvent::AppendedExchange { .. } + | BlocklistAIHistoryEvent::UpdatedTodoList { .. } + | BlocklistAIHistoryEvent::UpdatedAutoexecuteOverride { .. } + | BlocklistAIHistoryEvent::StartedNewConversation { .. } + | BlocklistAIHistoryEvent::SetActiveConversation { .. } => { + ctx.notify(); + } + _ => {} + } + } }, ); - let safe_mode_settings = SafeModeSettings::handle(ctx); - ctx.subscribe_to_model(&safe_mode_settings, |me, _, event, ctx| { - me.handle_safe_mode_settings_changed_event(event, ctx) - }); - - ctx.subscribe_to_model(&AIExecutionProfilesModel::handle(ctx), |me, _, _, ctx| { - let terminal_view_id = me.terminal_view_id; - if let Some(view) = me.ask_user_question_view.clone() { - view.update(ctx, |v, ctx| { - v.refresh_speedbump_dropdown_selection(terminal_view_id, ctx); - }); - } - }); - - let detected_links_state: DetectedLinksState = Default::default(); - let secret_redaction_state = SecretRedactionState::default(); - - ctx.subscribe_to_model(&find_model, |me, _, event, ctx| match event { - FindEvent::UpdatedFocusedMatch => { - me.handle_find_match_focus_change(ctx); - ctx.notify(); - } - FindEvent::RanFind => ctx.notify(), + let font_settings_handle = FontSettings::handle(ctx); + ctx.subscribe_to_model(&font_settings_handle, |_, _, _, ctx| { + ctx.notify(); }); - // Input Mode affects styling of AI blocks -- in particular, the spacing and border between - // a user query block and a subsequent action (e.g. requested command, suggested code diff) - // block. ctx.subscribe_to_model( &InputModeSettings::handle(ctx), |_, _, event, ctx| match event { @@ -1217,7 +1173,13 @@ impl AIBlock { }, ); - Self::register_action_model_subscription(&action_model, ctx); + ctx.subscribe_to_model(&AISettings::handle(ctx), |_, _, event, ctx| match event { + AISettingsChangedEvent::ThinkingDisplayMode { .. } + | AISettingsChangedEvent::OrchestrationMessageDisplayMode { .. } => { + ctx.notify(); + } + _ => {} + }); ctx.subscribe_to_model(&active_session, |me, _, event, ctx| match event { ActiveSessionEvent::UpdatedPwd => { @@ -1226,19 +1188,6 @@ impl AIBlock { ActiveSessionEvent::Bootstrapped => {} }); - ctx.subscribe_to_model(&get_relevant_files_controller, |me, _, event, ctx| { - if let GetRelevantFilesControllerEvent::Success { action_id, .. } = event { - if me.requested_action_ids.contains(action_id) { - ctx.notify(); - } - } - }); - - let manage_rules_button = ctx.add_typed_action_view(|_| { - ActionButton::new("Manage rules", NakedTheme) - .on_click(|ctx| ctx.dispatch_typed_action(AIBlockAction::OpenAIFactCollection)) - }); - ctx.subscribe_to_model(&AIRequestUsageModel::handle(ctx), |me, _, event, ctx| { if let AIRequestUsageModelEvent::RequestBonusRefunded { requests_refunded, @@ -1266,103 +1215,168 @@ impl AIBlock { } }); - // Note: UpdatedStreamingExchange is handled by the dedicated on_updated_output() - // callback in model_impl.rs, so we don't need to respond to it here. - ctx.subscribe_to_model( - &BlocklistAIHistoryModel::handle(ctx), - |me, _, event, ctx| { - if event - .terminal_view_id() - .is_none_or(|id| id == me.terminal_view_id) - { - match event { - BlocklistAIHistoryEvent::AppendedExchange { .. } - | BlocklistAIHistoryEvent::UpdatedTodoList { .. } - | BlocklistAIHistoryEvent::UpdatedAutoexecuteOverride { .. } - | BlocklistAIHistoryEvent::StartedNewConversation { .. } - | BlocklistAIHistoryEvent::SetActiveConversation { .. } => { - ctx.notify(); + // Subscriptions below are only needed for live (non-restored) blocks. + if !model.is_restored() { + ctx.subscribe_to_model( + &AISettings::handle(ctx), + move |me, settings_model, event, ctx| match event { + AISettingsChangedEvent::AgentModeExecuteReadonlyCommands { .. } => { + if let AutonomySettingSpeedbump::ShouldShowForAutoexecutingReadonlyCommands { + checked, + .. + } = &mut me.autonomy_setting_speedbump + { + *checked = *settings_model + .as_ref(ctx) + .agent_mode_execute_read_only_commands; + } else { + me.autonomy_setting_speedbump = AutonomySettingSpeedbump::None; } - _ => {} + ctx.notify(); } - } - }, - ); + AISettingsChangedEvent::AgentModeCodingPermissions { .. } => { + match &mut me.autonomy_setting_speedbump { + AutonomySettingSpeedbump::ShouldShowForFileAccess { checked, .. } => { + *checked = matches!( + *settings_model.as_ref(ctx).agent_mode_coding_permissions, + AgentModeCodingPermissionsType::AlwaysAllowReading + ); + } + AutonomySettingSpeedbump::ShouldShowForCodebaseSearchFileAccess { + selected_option, + .. + } => { + *selected_option = match *settings_model + .as_ref(ctx) + .agent_mode_coding_permissions + { + AgentModeCodingPermissionsType::AlwaysAllowReading => Some(0), + AgentModeCodingPermissionsType::AllowReadingSpecificFiles => { + Some(1) + } + AgentModeCodingPermissionsType::AlwaysAskBeforeReading => None, + }; + } + _ => {} + } + ctx.notify(); + } + _ => {} + }, + ); - ctx.subscribe_to_model( - cli_subagent_controller, - move |me, _, event, ctx| match event { - CLISubagentEvent::SpawnedSubagent { - initial_requested_command_action_id: Some(initial_requested_command_action_id), - .. - } => { - me.expand_requested_command_view(initial_requested_command_action_id, ctx); + let safe_mode_settings = SafeModeSettings::handle(ctx); + ctx.subscribe_to_model(&safe_mode_settings, |me, _, event, ctx| { + me.handle_safe_mode_settings_changed_event(event, ctx) + }); + + ctx.subscribe_to_model(&AIExecutionProfilesModel::handle(ctx), |me, _, _, ctx| { + let terminal_view_id = me.terminal_view_id; + if let Some(view) = me.ask_user_question_view.clone() { + view.update(ctx, |v, ctx| { + v.refresh_speedbump_dropdown_selection(terminal_view_id, ctx); + }); } - CLISubagentEvent::FinishedSubagent { - initial_requested_command_action_id: Some(initial_requested_command_action_id), - .. - } => { - me.collapse_requested_command_view(initial_requested_command_action_id, ctx); + }); + + Self::register_action_model_subscription(&action_model, ctx); + + ctx.subscribe_to_model(&get_relevant_files_controller, |me, _, event, ctx| { + if let GetRelevantFilesControllerEvent::Success { action_id, .. } = event { + if me.requested_action_ids.contains(action_id) { + ctx.notify(); + } } - CLISubagentEvent::UpdatedControl { - requested_command_action_id: Some(requested_command_action_id), - .. - } => { - if let Some(requested_command_view) = - me.requested_commands.get(requested_command_action_id) + }); + + ctx.subscribe_to_model( + cli_subagent_controller, + move |me, _, event, ctx| match event { + CLISubagentEvent::SpawnedSubagent { + initial_requested_command_action_id: + Some(initial_requested_command_action_id), + .. + } => { + me.expand_requested_command_view(initial_requested_command_action_id, ctx); + } + CLISubagentEvent::FinishedSubagent { + initial_requested_command_action_id: + Some(initial_requested_command_action_id), + .. + } => { + me.collapse_requested_command_view( + initial_requested_command_action_id, + ctx, + ); + } + CLISubagentEvent::UpdatedControl { + requested_command_action_id: Some(requested_command_action_id), + .. + } => { + if let Some(requested_command_view) = + me.requested_commands.get(requested_command_action_id) + { + requested_command_view + .view + .update(ctx, |_, ctx| ctx.notify()); + } + } + _ => {} + }, + ); + + ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| { + if let ModelEvent::BlockCompleted(block_completed_event) = event { + if me.is_finished() || me.requested_action_ids.is_empty() { + return; + } + let terminal_model = me.terminal_model.lock(); + if terminal_model + .block_list() + .block_with_id(&block_completed_event.block_id) + .and_then(|block| block.agent_interaction_metadata()) + .is_some_and(|metadata| { + metadata + .requested_command_action_id() + .is_some_and(|id| me.requested_action_ids.contains(id)) + }) { - requested_command_view - .view - .update(ctx, |_, ctx| ctx.notify()); + ctx.notify(); } } - _ => {} - }, - ); + }); - ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| { - if let ModelEvent::BlockCompleted(block_completed_event) = event { - let terminal_model = me.terminal_model.lock(); - if terminal_model - .block_list() - .block_with_id(&block_completed_event.block_id) - .and_then(|block| block.agent_interaction_metadata()) - .is_some_and(|metadata| { - metadata - .requested_command_action_id() - .is_some_and(|id| me.requested_action_ids.contains(id)) - }) - { - ctx.notify(); - } + if FeatureFlag::AgentView.is_enabled() { + ctx.subscribe_to_model(&agent_view_controller, |_, _, _, ctx| ctx.notify()); + } + + // Handoff prep emits ambient-agent events before submit, while still composing. + // Only the run lifecycle events can change `is_cloud_agent_pre_first_exchange` + // for this block's footer. + if let Some(ambient_agent_view_model) = ambient_agent_view_model.as_ref() { + ctx.subscribe_to_model(ambient_agent_view_model, |_, _, event, ctx| match event { + AmbientAgentViewModelEvent::DispatchedAgent + | AmbientAgentViewModelEvent::FollowupDispatched + | AmbientAgentViewModelEvent::SessionReady { .. } + | AmbientAgentViewModelEvent::ExecutionSessionReady { .. } + | AmbientAgentViewModelEvent::Failed { .. } + | AmbientAgentViewModelEvent::NeedsGithubAuth + | AmbientAgentViewModelEvent::Cancelled + | AmbientAgentViewModelEvent::HarnessCommandStarted { .. } => ctx.notify(), + _ => {} + }); } - }); - if FeatureFlag::AgentView.is_enabled() { - ctx.subscribe_to_model(&agent_view_controller, |_, _, _, ctx| ctx.notify()); - } - - // Handoff prep emits ambient-agent events before submit, while still composing. - // Only the run lifecycle events can change `is_cloud_agent_pre_first_exchange` - // for this block's footer. - if let Some(ambient_agent_view_model) = ambient_agent_view_model.as_ref() { - ctx.subscribe_to_model(ambient_agent_view_model, |_, _, event, ctx| match event { - AmbientAgentViewModelEvent::DispatchedAgent - | AmbientAgentViewModelEvent::FollowupDispatched - | AmbientAgentViewModelEvent::SessionReady { .. } - | AmbientAgentViewModelEvent::ExecutionSessionReady { .. } - | AmbientAgentViewModelEvent::Failed { .. } - | AmbientAgentViewModelEvent::NeedsGithubAuth - | AmbientAgentViewModelEvent::Cancelled - | AmbientAgentViewModelEvent::HarnessCommandStarted { .. } => ctx.notify(), - _ => {} + ctx.subscribe_to_model(&context_model, |_, _, event, ctx| { + if let BlocklistAIContextEvent::UpdatedPendingContext { .. } = event { + ctx.notify(); + } }); } - ctx.subscribe_to_model(&context_model, |_, _, event, ctx| { - if let BlocklistAIContextEvent::UpdatedPendingContext { .. } = event { - ctx.notify(); - } + let manage_rules_button = ctx.add_typed_action_view(|_| { + ActionButton::new("Manage rules", NakedTheme) + .on_click(|ctx| ctx.dispatch_typed_action(AIBlockAction::OpenAIFactCollection)) }); let review_changes_button = ctx.add_typed_action_view(|_| {