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> {
use crate::ai::execution_profiles::PermissionMode;
let is_active = matches!(props.mode, PermissionMode::GalaxyDecides | PermissionMode::AllowAll);
let is_locked = props.is_locked || props.mode == PermissionMode::ReadOnly;
let icon = if is_active {
Icon::FastForwardFilled
} else {
Icon::FastForward
let is_locked = props.is_locked;
let (icon, next_mode) = match props.mode {
PermissionMode::ReadOnly => (Icon::Eye, PermissionMode::AskFirst),
PermissionMode::AskFirst => (Icon::MessageChatSquare, PermissionMode::GalaxyDecides),
PermissionMode::GalaxyDecides => (Icon::GalaxyLogo, PermissionMode::AllowAll),
PermissionMode::AllowAll => (Icon::CheckCircleBroken, PermissionMode::ReadOnly),
};
let icon_size = get_icon_size(appearance);
let icon = Container::new(
@@ -988,20 +989,28 @@ fn render_permission_mode_button(
)
.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 {
"Permission mode is managed by the profile"
} else if is_active {
"Galaxy Decides · switch to Ask First"
format!("{} · permission mode is managed by the profile", props.mode.label())
} else {
"Ask First · switch to Galaxy Decides"
format!("{} · switch to {}", props.mode.label(), next_mode.label())
};
render_warping_indicator_button(
props.button_handle.clone(),
appearance,
icon,
content,
props.keystroke,
tooltip_text.to_string(),
tooltip_text,
is_active,
is_locked,
move |ctx| {
+10 -10
View File
@@ -201,8 +201,8 @@ pub struct ExecutionProfileEditorView {
command_denylist_editor: ViewHandle<SubmittableTextInput>,
command_allowlist_editor: ViewHandle<SubmittableTextInput>,
command_allowlist_mouse_state_handles: Vec<MouseStateHandle>,
orchestration_provider_switches: Vec<(String, SwitchStateHandle)>,
all_providers_switch: SwitchStateHandle,
orchestration_provider_checkboxes: Vec<(String, MouseStateHandle)>,
all_providers_checkbox: MouseStateHandle,
command_denylist_mouse_state_handles: Vec<MouseStateHandle>,
command_denylist_tooltip_mouse_state_handles: Vec<MouseStateHandle>,
mcp_denylist_dropdown: ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
@@ -364,8 +364,8 @@ impl ExecutionProfileEditorView {
command_denylist_editor,
command_allowlist_editor,
command_allowlist_mouse_state_handles: Vec::new(),
orchestration_provider_switches: Vec::new(),
all_providers_switch: Default::default(),
orchestration_provider_checkboxes: Vec::new(),
all_providers_checkbox: Default::default(),
command_denylist_mouse_state_handles,
command_denylist_tooltip_mouse_state_handles: profile_data
.command_denylist
@@ -555,16 +555,16 @@ impl ExecutionProfileEditorView {
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)
.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();
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();
self.orchestration_provider_switches.retain(|(name, _)| names.contains(name));
for name in names { if !self.orchestration_provider_switches.iter().any(|(existing, _)| existing == &name) {
self.orchestration_provider_switches.push((name, Default::default()));
self.orchestration_provider_checkboxes.retain(|(name, _)| names.contains(name));
for name in names { if !self.orchestration_provider_checkboxes.iter().any(|(existing, _)| existing == &name) {
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
.command_denylist
@@ -9,6 +9,7 @@ use warpui::elements::{
};
use warpui::fonts::{Properties, Weight};
use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
use warpui::ui_components::text::Span;
use warpui::{AppContext, Element, SingletonEntity, ViewHandle};
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()
}
fn render_orchestration_providers(view: &ExecutionProfileEditorView, profile: &AIExecutionProfile, appearance: &Appearance) -> Box<dyn Element> {
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));
fn render_orchestration_providers(
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 names: Vec<_> = view.orchestration_provider_switches.iter().map(|(name, _)| name.clone()).collect();
let selected = if all { Some(names.clone()) } else { None };
let switch = appearance.ui_builder().switch(view.all_providers_switch.clone()).check(all).build()
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(ExecutionProfileEditorViewAction::SetOrchestrationProviders { providers: selected.clone() })).finish();
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());
for (name, state) in &view.orchestration_provider_switches {
let included = profile.includes_orchestration_provider(name);
let mut next = profile.orchestration_providers.clone().unwrap_or_else(|| names.clone());
if included { next.retain(|provider| provider != name); } else { next.push(name.clone()); }
let switch = appearance.ui_builder().switch(state.clone()).check(included).build()
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(ExecutionProfileEditorViewAction::SetOrchestrationProviders { providers: Some(next.clone()) })).finish();
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());
let names: Vec<_> = view.orchestration_provider_checkboxes
.iter()
.map(|(name, _)| name.clone())
.collect();
let selected = if all { Some(names) } else { None };
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 mut next = profile.orchestration_providers.clone().unwrap_or_default();
if included {
next.retain(|provider| provider != name);
} else {
next.push(name.clone());
}
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.).finish()
Container::new(column.finish()).with_margin_top(16.).with_margin_bottom(12.).finish()
}
fn display_mcp_name(uuid: &Uuid, app: &AppContext) -> String {
+5 -8
View File
@@ -27437,15 +27437,12 @@ impl TypedActionView for TerminalView {
ToggleAutoexecuteMode => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles, ctx| {
let profile = profiles.active_profile(Some(self.view_id), ctx);
use crate::ai::execution_profiles::PermissionMode;
let next = match profile.data().permission_mode {
crate::ai::execution_profiles::PermissionMode::ReadOnly => return,
crate::ai::execution_profiles::PermissionMode::AskFirst => {
crate::ai::execution_profiles::PermissionMode::GalaxyDecides
}
crate::ai::execution_profiles::PermissionMode::GalaxyDecides
| crate::ai::execution_profiles::PermissionMode::AllowAll => {
crate::ai::execution_profiles::PermissionMode::AskFirst
}
PermissionMode::ReadOnly => PermissionMode::AskFirst,
PermissionMode::AskFirst => PermissionMode::GalaxyDecides,
PermissionMode::GalaxyDecides => PermissionMode::AllowAll,
PermissionMode::AllowAll => PermissionMode::ReadOnly,
};
profiles.set_permission_mode(*profile.id(), next, ctx);
});