diff --git a/crates/agent_ui/src/connection_view/thread_view.rs b/crates/agent_ui/src/connection_view/thread_view.rs index 44f9e78a2bb47af6cb171194fbd5a34de7383f1b..030f6c5431eb79258be60f9d0139b8757611aa71 100644 --- a/crates/agent_ui/src/connection_view/thread_view.rs +++ b/crates/agent_ui/src/connection_view/thread_view.rs @@ -2674,6 +2674,14 @@ impl ThreadView { return div().into_any_element(); } + let is_generating = self.thread.read(cx).status() != ThreadStatus::Idle; + if let Some(model_selector) = &self.model_selector { + model_selector.update(cx, |selector, _| selector.set_disabled(is_generating)); + } + if let Some(profile_selector) = &self.profile_selector { + profile_selector.update(cx, |selector, _| selector.set_disabled(is_generating)); + } + let focus_handle = self.message_editor.focus_handle(cx); let editor_bg_color = cx.theme().colors().editor_background; let editor_expanded = self.editor_expanded; @@ -3223,6 +3231,7 @@ impl ThreadView { return None; } + let is_generating = self.thread.read(cx).status() != ThreadStatus::Idle; let thinking = thread.thinking_enabled(); let (tooltip_label, icon, color) = if thinking { @@ -3244,8 +3253,13 @@ impl ThreadView { let thinking_toggle = IconButton::new("thinking-mode", icon) .icon_size(IconSize::Small) .icon_color(color) - .tooltip(move |_, cx| { - Tooltip::for_action_in(tooltip_label, &ToggleThinkingMode, &focus_handle, cx) + .disabled(is_generating) + .tooltip(move |window, cx| { + if is_generating { + Tooltip::text("Disabled until generation is done")(window, cx) + } else { + Tooltip::for_action_in(tooltip_label, &ToggleThinkingMode, &focus_handle, cx) + } }) .on_click(cx.listener(move |this, _, _window, cx| { if let Some(thread) = this.as_native_thread(cx) { @@ -3277,6 +3291,7 @@ impl ThreadView { let right_btn = self.render_effort_selector( model.supported_effort_levels(), thread.thinking_effort().cloned(), + is_generating, cx, ); @@ -3291,6 +3306,7 @@ impl ThreadView { &self, supported_effort_levels: Vec, selected_effort: Option, + disabled: bool, cx: &Context, ) -> impl IntoElement { let weak_self = cx.weak_entity(); @@ -3359,6 +3375,7 @@ impl ThreadView { PopoverMenu::new("effort-selector") .trigger_with_tooltip( ButtonLike::new_rounded_right("effort-selector-trigger") + .disabled(disabled) .selected_style(ButtonStyle::Tinted(TintColor::Accent)) .child(Label::new(label).size(LabelSize::Small).color(label_color)) .child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted)), @@ -7722,6 +7739,9 @@ impl Render for ThreadView { this.toggle_fast_mode(cx); })) .on_action(cx.listener(|this, _: &ToggleThinkingMode, _window, cx| { + if this.thread.read(cx).status() != ThreadStatus::Idle { + return; + } if let Some(thread) = this.as_native_thread(cx) { thread.update(cx, |thread, cx| { thread.set_thinking_enabled(!thread.thinking_enabled(), cx); @@ -7729,9 +7749,19 @@ impl Render for ThreadView { } })) .on_action(cx.listener(|this, _: &CycleThinkingEffort, _window, cx| { + if this.thread.read(cx).status() != ThreadStatus::Idle { + return; + } this.cycle_thinking_effort(cx); })) - .on_action(cx.listener(Self::toggle_thinking_effort_menu)) + .on_action( + cx.listener(|this, action: &ToggleThinkingEffortMenu, window, cx| { + if this.thread.read(cx).status() != ThreadStatus::Idle { + return; + } + this.toggle_thinking_effort_menu(action, window, cx); + }), + ) .on_action(cx.listener(|this, _: &SendNextQueuedMessage, window, cx| { this.send_queued_message_at_index(0, true, window, cx); })) @@ -7749,6 +7779,9 @@ impl Render for ThreadView { cx.notify(); })) .on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| { + if this.thread.read(cx).status() != ThreadStatus::Idle { + return; + } if let Some(config_options_view) = this.config_options_view.clone() { let handled = config_options_view.update(cx, |view, cx| { view.toggle_category_picker( @@ -7769,6 +7802,9 @@ impl Render for ThreadView { } })) .on_action(cx.listener(|this, _: &CycleModeSelector, window, cx| { + if this.thread.read(cx).status() != ThreadStatus::Idle { + return; + } if let Some(config_options_view) = this.config_options_view.clone() { let handled = config_options_view.update(cx, |view, cx| { view.cycle_category_option( @@ -7793,6 +7829,9 @@ impl Render for ThreadView { } })) .on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| { + if this.thread.read(cx).status() != ThreadStatus::Idle { + return; + } if let Some(config_options_view) = this.config_options_view.clone() { let handled = config_options_view.update(cx, |view, cx| { view.toggle_category_picker( @@ -7812,6 +7851,9 @@ impl Render for ThreadView { } })) .on_action(cx.listener(|this, _: &CycleFavoriteModels, window, cx| { + if this.thread.read(cx).status() != ThreadStatus::Idle { + return; + } if let Some(config_options_view) = this.config_options_view.clone() { let handled = config_options_view.update(cx, |view, cx| { view.cycle_category_option( diff --git a/crates/agent_ui/src/model_selector_popover.rs b/crates/agent_ui/src/model_selector_popover.rs index 257337b6b0b8a39645bc38b4d814b250d7b5e1f9..7a4e9dbf8633680fe9c6ee3bda4acdb0ff5b1478 100644 --- a/crates/agent_ui/src/model_selector_popover.rs +++ b/crates/agent_ui/src/model_selector_popover.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use acp_thread::{AgentModelIcon, AgentModelInfo, AgentModelSelector}; use fs::Fs; -use gpui::{Entity, FocusHandle}; +use gpui::{AnyView, Entity, FocusHandle}; use picker::popover_menu::PickerPopoverMenu; use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*}; @@ -13,6 +13,7 @@ use crate::{ModelSelector, model_selector::acp_model_selector}; pub struct ModelSelectorPopover { selector: Entity, menu_handle: PopoverMenuHandle, + disabled: bool, } impl ModelSelectorPopover { @@ -30,10 +31,18 @@ impl ModelSelectorPopover { acp_model_selector(selector, agent_server, fs, focus_handle.clone(), window, cx) }), menu_handle, + disabled: false, } } + pub fn set_disabled(&mut self, disabled: bool) { + self.disabled = disabled; + } + pub fn toggle(&self, window: &mut Window, cx: &mut Context) { + if self.disabled { + return; + } self.menu_handle.toggle(window, cx); } @@ -42,6 +51,9 @@ impl ModelSelectorPopover { } pub fn cycle_favorite_models(&self, window: &mut Window, cx: &mut Context) { + if self.disabled { + return; + } self.selector.update(cx, |selector, cx| { selector.delegate.cycle_favorite_models(window, cx); }); @@ -61,23 +73,31 @@ impl Render for ModelSelectorPopover { let (color, icon) = if self.menu_handle.is_deployed() { (Color::Accent, IconName::ChevronUp) + } else if self.disabled { + (Color::Disabled, IconName::ChevronDown) } else { (Color::Muted, IconName::ChevronDown) }; let show_cycle_row = selector.delegate.favorites_count() > 1; + let disabled = self.disabled; - let tooltip = Tooltip::element({ - move |_, _cx| { - ModelSelectorTooltip::new() - .show_cycle_row(show_cycle_row) - .into_any_element() - } - }); + let tooltip: Box AnyView> = if disabled { + Box::new(Tooltip::text("Disabled until generation is done")) + } else { + Box::new(Tooltip::element({ + move |_, _cx| { + ModelSelectorTooltip::new() + .show_cycle_row(show_cycle_row) + .into_any_element() + } + })) + }; PickerPopoverMenu::new( self.selector.clone(), ButtonLike::new("active-model") + .disabled(self.disabled) .selected_style(ButtonStyle::Tinted(TintColor::Accent)) .when_some(model_icon, |this, icon| { this.child( @@ -95,7 +115,17 @@ impl Render for ModelSelectorPopover { .size(LabelSize::Small) .ml_0p5(), ) - .child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)), + .child( + Icon::new(icon) + .map(|this| { + if self.disabled { + this.color(Color::Disabled) + } else { + this.color(Color::Muted) + } + }) + .size(IconSize::XSmall), + ), tooltip, gpui::Corner::BottomRight, cx, diff --git a/crates/agent_ui/src/profile_selector.rs b/crates/agent_ui/src/profile_selector.rs index 926549c22f88bcb0937dddf7c3ff1b32060ed297..f785c936a643f4280121d083831eba4c909bc0f5 100644 --- a/crates/agent_ui/src/profile_selector.rs +++ b/crates/agent_ui/src/profile_selector.rs @@ -5,8 +5,8 @@ use agent_settings::{ use fs::Fs; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; use gpui::{ - Action, AnyElement, App, BackgroundExecutor, Context, DismissEvent, Entity, FocusHandle, - Focusable, ForegroundExecutor, SharedString, Subscription, Task, Window, + Action, AnyElement, AnyView, App, BackgroundExecutor, Context, DismissEvent, Entity, + FocusHandle, Focusable, ForegroundExecutor, SharedString, Subscription, Task, Window, }; use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu}; use settings::{Settings as _, SettingsStore, update_settings_file}; @@ -34,6 +34,7 @@ pub trait ProfileProvider { pub struct ProfileSelector { profiles: AvailableProfiles, pending_refresh: bool, + disabled: bool, fs: Arc, provider: Arc, picker: Option>>, @@ -57,6 +58,7 @@ impl ProfileSelector { Self { profiles: AgentProfile::available_profiles(cx), pending_refresh: false, + disabled: false, fs, provider, picker: None, @@ -70,7 +72,19 @@ impl ProfileSelector { self.picker_handle.clone() } + pub fn set_disabled(&mut self, disabled: bool) { + self.disabled = disabled; + } + + pub fn is_disabled(&self) -> bool { + self.disabled + } + pub fn cycle_profile(&mut self, cx: &mut Context) { + if self.disabled { + return; + } + if !self.provider.profiles_supported(cx) { return; } @@ -175,6 +189,7 @@ impl Render for ProfileSelector { }; let trigger_button = Button::new("profile-selector", selected_profile) + .disabled(self.disabled) .label_size(LabelSize::Small) .color(Color::Muted) .icon(icon) @@ -183,10 +198,12 @@ impl Render for ProfileSelector { .icon_color(Color::Muted) .selected_style(ButtonStyle::Tinted(TintColor::Accent)); - PickerPopoverMenu::new( - picker, - trigger_button, - Tooltip::element({ + let disabled = self.disabled; + + let tooltip: Box AnyView> = if disabled { + Box::new(Tooltip::text("Disabled until generation is done")) + } else { + Box::new(Tooltip::element({ move |_window, cx| { let container = || h_flex().gap_1().justify_between(); v_flex() @@ -206,7 +223,13 @@ impl Render for ProfileSelector { ) .into_any() } - }), + })) + }; + + PickerPopoverMenu::new( + picker, + trigger_button, + tooltip, gpui::Corner::BottomRight, cx, )