Detailed changes
@@ -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<LanguageModelEffortLevel>,
selected_effort: Option<String>,
+ disabled: bool,
cx: &Context<Self>,
) -> 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(
@@ -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<ModelSelector>,
menu_handle: PopoverMenuHandle<ModelSelector>,
+ 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<Self>) {
+ 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<Self>) {
+ 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<dyn Fn(&mut Window, &mut App) -> 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,
@@ -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<dyn Fs>,
provider: Arc<dyn ProfileProvider>,
picker: Option<Entity<Picker<ProfilePickerDelegate>>>,
@@ -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<Self>) {
+ 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<dyn Fn(&mut Window, &mut App) -> 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,
)