Polish provider selection and cycle all permission modes

This commit is contained in:
2026-10-05 16:23:59 -05:00
parent 679306a103
commit a6492d2d95
4 changed files with 89 additions and 44 deletions
+20 -11
View File
@@ -970,11 +970,12 @@ fn render_permission_mode_button(
) -> Box<dyn Element> { ) -> Box<dyn Element> {
use crate::ai::execution_profiles::PermissionMode; use crate::ai::execution_profiles::PermissionMode;
let is_active = matches!(props.mode, PermissionMode::GalaxyDecides | PermissionMode::AllowAll); let is_active = matches!(props.mode, PermissionMode::GalaxyDecides | PermissionMode::AllowAll);
let is_locked = props.is_locked || props.mode == PermissionMode::ReadOnly; let is_locked = props.is_locked;
let icon = if is_active { let (icon, next_mode) = match props.mode {
Icon::FastForwardFilled PermissionMode::ReadOnly => (Icon::Eye, PermissionMode::AskFirst),
} else { PermissionMode::AskFirst => (Icon::MessageChatSquare, PermissionMode::GalaxyDecides),
Icon::FastForward PermissionMode::GalaxyDecides => (Icon::GalaxyLogo, PermissionMode::AllowAll),
PermissionMode::AllowAll => (Icon::CheckCircleBroken, PermissionMode::ReadOnly),
}; };
let icon_size = get_icon_size(appearance); let icon_size = get_icon_size(appearance);
let icon = Container::new( let icon = Container::new(
@@ -988,20 +989,28 @@ fn render_permission_mode_button(
) )
.finish(); .finish();
let content = Flex::row()
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_spacing(6.)
.with_child(icon)
.with_child(
Text::new(props.mode.label(), appearance.ui_font_family(), get_keybinding_font_size(appearance))
.with_color(appearance.theme().active_ui_text_color().into())
.finish(),
)
.finish();
let tooltip_text = if is_locked { let tooltip_text = if is_locked {
"Permission mode is managed by the profile" format!("{} · permission mode is managed by the profile", props.mode.label())
} else if is_active {
"Galaxy Decides · switch to Ask First"
} else { } else {
"Ask First · switch to Galaxy Decides" format!("{} · switch to {}", props.mode.label(), next_mode.label())
}; };
render_warping_indicator_button( render_warping_indicator_button(
props.button_handle.clone(), props.button_handle.clone(),
appearance, appearance,
icon, content,
props.keystroke, props.keystroke,
tooltip_text.to_string(), tooltip_text,
is_active, is_active,
is_locked, is_locked,
move |ctx| { move |ctx| {
+10 -10
View File
@@ -201,8 +201,8 @@ pub struct ExecutionProfileEditorView {
command_denylist_editor: ViewHandle<SubmittableTextInput>, command_denylist_editor: ViewHandle<SubmittableTextInput>,
command_allowlist_editor: ViewHandle<SubmittableTextInput>, command_allowlist_editor: ViewHandle<SubmittableTextInput>,
command_allowlist_mouse_state_handles: Vec<MouseStateHandle>, command_allowlist_mouse_state_handles: Vec<MouseStateHandle>,
orchestration_provider_switches: Vec<(String, SwitchStateHandle)>, orchestration_provider_checkboxes: Vec<(String, MouseStateHandle)>,
all_providers_switch: SwitchStateHandle, all_providers_checkbox: MouseStateHandle,
command_denylist_mouse_state_handles: Vec<MouseStateHandle>, command_denylist_mouse_state_handles: Vec<MouseStateHandle>,
command_denylist_tooltip_mouse_state_handles: Vec<MouseStateHandle>, command_denylist_tooltip_mouse_state_handles: Vec<MouseStateHandle>,
mcp_denylist_dropdown: ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>, mcp_denylist_dropdown: ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
@@ -364,8 +364,8 @@ impl ExecutionProfileEditorView {
command_denylist_editor, command_denylist_editor,
command_allowlist_editor, command_allowlist_editor,
command_allowlist_mouse_state_handles: Vec::new(), command_allowlist_mouse_state_handles: Vec::new(),
orchestration_provider_switches: Vec::new(), orchestration_provider_checkboxes: Vec::new(),
all_providers_switch: Default::default(), all_providers_checkbox: Default::default(),
command_denylist_mouse_state_handles, command_denylist_mouse_state_handles,
command_denylist_tooltip_mouse_state_handles: profile_data command_denylist_tooltip_mouse_state_handles: profile_data
.command_denylist .command_denylist
@@ -555,16 +555,16 @@ impl ExecutionProfileEditorView {
self.command_allowlist_mouse_state_handles = current_permissions.command_allowlist.iter().map(|_| Default::default()).collect(); self.command_allowlist_mouse_state_handles = current_permissions.command_allowlist.iter().map(|_| Default::default()).collect();
let mut names: Vec<_> = LLMPreferences::as_ref(app).get_base_llm_choices_for_agent_mode(app) let mut names: Vec<_> = LLMPreferences::as_ref(app).get_base_llm_choices_for_agent_mode(app)
.filter(|model| !crate::ai::custom_model_routers::is_custom_router_id(model.id.as_str())) .filter(|model| !matches!(model.id.as_str(), "none" | "placeholder") && !crate::ai::custom_model_routers::is_custom_router_id(model.id.as_str()))
.filter_map(|model| model.description.clone()).collect(); .filter_map(|model| model.description.clone()).collect();
names.extend(AISettings::as_ref(app).openai_providers.value().iter().map(|provider| provider.name.clone())); names.extend(AISettings::as_ref(app).openai_providers.value().iter().map(|provider| provider.name.clone()));
if let Some(included) = &current_permissions.orchestration_providers { names.extend(included.iter().cloned()); } names.retain(|name| !name.trim().is_empty());
names.sort(); names.dedup(); names.sort(); names.dedup();
self.orchestration_provider_switches.retain(|(name, _)| names.contains(name)); self.orchestration_provider_checkboxes.retain(|(name, _)| names.contains(name));
for name in names { if !self.orchestration_provider_switches.iter().any(|(existing, _)| existing == &name) { for name in names { if !self.orchestration_provider_checkboxes.iter().any(|(existing, _)| existing == &name) {
self.orchestration_provider_switches.push((name, Default::default())); self.orchestration_provider_checkboxes.push((name, Default::default()));
}} }}
self.orchestration_provider_switches.sort_by(|a, b| a.0.cmp(&b.0)); self.orchestration_provider_checkboxes.sort_by(|a, b| a.0.cmp(&b.0));
self.command_denylist_mouse_state_handles = current_permissions self.command_denylist_mouse_state_handles = current_permissions
.command_denylist .command_denylist
@@ -9,6 +9,7 @@ use warpui::elements::{
}; };
use warpui::fonts::{Properties, Weight}; use warpui::fonts::{Properties, Weight};
use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles}; use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
use warpui::ui_components::text::Span;
use warpui::{AppContext, Element, SingletonEntity, ViewHandle}; use warpui::{AppContext, Element, SingletonEntity, ViewHandle};
use super::{ExecutionProfileEditorView, ExecutionProfileEditorViewAction}; use super::{ExecutionProfileEditorView, ExecutionProfileEditorViewAction};
@@ -627,23 +628,61 @@ fn render_command_allowlist_section(view: &ExecutionProfileEditorView, profile:
.with_child(render_input_list(None, items, Some(&view.command_allowlist_editor), appearance)).finish() .with_child(render_input_list(None, items, Some(&view.command_allowlist_editor), appearance)).finish()
} }
fn render_orchestration_providers(view: &ExecutionProfileEditorView, profile: &AIExecutionProfile, appearance: &Appearance) -> Box<dyn Element> { fn render_orchestration_providers(
let mut column = Flex::column().with_child(create_section_header("Orchestration providers", "Only included providers can supply models for sub-agents and code reviews. All providers includes providers added later.", appearance)); view: &ExecutionProfileEditorView,
profile: &AIExecutionProfile,
appearance: &Appearance,
) -> Box<dyn Element> {
let mut column = Flex::column().with_child(create_section_header(
"Orchestration providers",
"Choose which providers can supply models for sub-agents and code reviews.",
appearance,
));
let all = profile.orchestration_providers.is_none(); let all = profile.orchestration_providers.is_none();
let names: Vec<_> = view.orchestration_provider_switches.iter().map(|(name, _)| name.clone()).collect(); let names: Vec<_> = view.orchestration_provider_checkboxes
let selected = if all { Some(names.clone()) } else { None }; .iter()
let switch = appearance.ui_builder().switch(view.all_providers_switch.clone()).check(all).build() .map(|(name, _)| name.clone())
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(ExecutionProfileEditorViewAction::SetOrchestrationProviders { providers: selected.clone() })).finish(); .collect();
column.add_child(Flex::row().with_main_axis_alignment(MainAxisAlignment::SpaceBetween).with_child(Text::new("All providers", appearance.ui_font_family(), 13.).with_color(appearance.theme().active_ui_text_color().into()).finish()).with_child(switch).finish()); let selected = if all { Some(names) } else { None };
for (name, state) in &view.orchestration_provider_switches { let checkbox = appearance.ui_builder()
.checkbox(view.all_providers_checkbox.clone(), Some(14.))
.with_label(Span::new("All providers", Default::default()))
.check(all)
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(ExecutionProfileEditorViewAction::SetOrchestrationProviders {
providers: selected.clone(),
});
})
.finish();
column.add_child(Container::new(checkbox).with_margin_top(12.).finish());
if !all {
let mut providers = Flex::column();
for (name, state) in &view.orchestration_provider_checkboxes {
let included = profile.includes_orchestration_provider(name); let included = profile.includes_orchestration_provider(name);
let mut next = profile.orchestration_providers.clone().unwrap_or_else(|| names.clone()); let mut next = profile.orchestration_providers.clone().unwrap_or_default();
if included { next.retain(|provider| provider != name); } else { next.push(name.clone()); } if included {
let switch = appearance.ui_builder().switch(state.clone()).check(included).build() next.retain(|provider| provider != name);
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(ExecutionProfileEditorViewAction::SetOrchestrationProviders { providers: Some(next.clone()) })).finish(); } else {
column.add_child(Container::new(Flex::row().with_main_axis_alignment(MainAxisAlignment::SpaceBetween).with_child(Text::new(name.clone(), appearance.ui_font_family(), 13.).with_color(appearance.theme().active_ui_text_color().into()).finish()).with_child(switch).finish()).with_margin_top(8.).finish()); next.push(name.clone());
} }
Container::new(column.finish()).with_margin_top(16.).finish() let checkbox = appearance.ui_builder()
.checkbox(state.clone(), Some(14.))
.with_label(Span::new(name.clone(), Default::default()))
.check(included)
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(ExecutionProfileEditorViewAction::SetOrchestrationProviders {
providers: Some(next.clone()),
});
})
.finish();
providers.add_child(Container::new(checkbox).with_margin_top(12.).finish());
}
column.add_child(Container::new(providers.finish()).with_margin_left(24.).finish());
}
Container::new(column.finish()).with_margin_top(16.).with_margin_bottom(12.).finish()
} }
fn display_mcp_name(uuid: &Uuid, app: &AppContext) -> String { fn display_mcp_name(uuid: &Uuid, app: &AppContext) -> String {
+5 -8
View File
@@ -27437,15 +27437,12 @@ impl TypedActionView for TerminalView {
ToggleAutoexecuteMode => { ToggleAutoexecuteMode => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles, ctx| { AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles, ctx| {
let profile = profiles.active_profile(Some(self.view_id), ctx); let profile = profiles.active_profile(Some(self.view_id), ctx);
use crate::ai::execution_profiles::PermissionMode;
let next = match profile.data().permission_mode { let next = match profile.data().permission_mode {
crate::ai::execution_profiles::PermissionMode::ReadOnly => return, PermissionMode::ReadOnly => PermissionMode::AskFirst,
crate::ai::execution_profiles::PermissionMode::AskFirst => { PermissionMode::AskFirst => PermissionMode::GalaxyDecides,
crate::ai::execution_profiles::PermissionMode::GalaxyDecides PermissionMode::GalaxyDecides => PermissionMode::AllowAll,
} PermissionMode::AllowAll => PermissionMode::ReadOnly,
crate::ai::execution_profiles::PermissionMode::GalaxyDecides
| crate::ai::execution_profiles::PermissionMode::AllowAll => {
crate::ai::execution_profiles::PermissionMode::AskFirst
}
}; };
profiles.set_permission_mode(*profile.id(), next, ctx); profiles.set_permission_mode(*profile.id(), next, ctx);
}); });