//! Settings UI for Galaxy Control installation and local automation permissions. use std::cell::RefCell; use std::collections::HashMap; #[cfg(target_os = "macos")] use galaxy_core::channel::ChannelState; use settings::Setting as _; use warpui::elements::{ChildView, Element, MouseStateHandle}; #[cfg(target_os = "macos")] use warpui::ui_components::button::ButtonVariant; #[cfg(target_os = "macos")] use warpui::ui_components::components::UiComponent; use warpui::{AppContext, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle}; use super::settings_page::{ render_body_item, LocalOnlyIconState, MatchData, PageType, SettingsPageMeta, SettingsPageViewHandle, SettingsWidget, }; use super::{SettingsSection, ToggleState}; use crate::appearance::Appearance; use crate::features::FeatureFlag; use crate::report_if_error; use crate::settings::{LocalControlMode, LocalControlModeSetting, LocalControlSettings}; #[cfg(target_os = "macos")] use crate::view_components::DismissibleToast; use crate::view_components::{Dropdown, DropdownItem}; #[cfg(target_os = "macos")] use crate::workspace::{cli_install, ToastStack}; #[derive(Clone, Debug, PartialEq)] pub enum ScriptingSettingsPageAction { SetLocalControlMode(LocalControlMode), #[cfg(target_os = "macos")] InstallGalaxyControlCli, } pub struct ScriptingSettingsPageView { page: PageType, local_only_icon_tooltip_states: RefCell>, local_control_mode_dropdown: ViewHandle>, #[cfg(target_os = "macos")] galaxyctrl_installing: bool, } impl ScriptingSettingsPageView { pub fn new(ctx: &mut ViewContext) -> Self { let local_control_mode_dropdown = ctx.add_typed_action_view(|ctx| { let mut dropdown = Dropdown::new(ctx); dropdown.set_top_bar_max_width(360.); dropdown }); Self::update_local_control_mode_dropdown(local_control_mode_dropdown.clone(), ctx); if FeatureFlag::GalaxyControlCli.is_enabled() { ctx.subscribe_to_model(&LocalControlSettings::handle(ctx), |view, _, _, ctx| { Self::update_local_control_mode_dropdown( view.local_control_mode_dropdown.clone(), ctx, ); ctx.notify(); }); } #[cfg(target_os = "macos")] let widgets: Vec>> = vec![ Box::new(GalaxyControlCliInstallWidget::default()), Box::new(LocalControlModeWidget), ]; #[cfg(not(target_os = "macos"))] let widgets: Vec>> = vec![Box::new(LocalControlModeWidget)]; Self { page: PageType::new_uncategorized(widgets, Some("Galaxy Control")), local_only_icon_tooltip_states: RefCell::new(HashMap::new()), local_control_mode_dropdown, #[cfg(target_os = "macos")] galaxyctrl_installing: false, } } fn update_local_control_mode_dropdown( dropdown: ViewHandle>, ctx: &mut ViewContext, ) { let current_mode = LocalControlSettings::as_ref(ctx).mode(); dropdown.update(ctx, |dropdown, ctx| { dropdown.set_items( LocalControlMode::ALL .into_iter() .map(|mode| { DropdownItem::new( mode.as_dropdown_label(), ScriptingSettingsPageAction::SetLocalControlMode(mode), ) }) .collect(), ctx, ); dropdown.set_selected_by_action( ScriptingSettingsPageAction::SetLocalControlMode(current_mode), ctx, ); }); } #[cfg(target_os = "macos")] fn install_galaxyctrl(&mut self, ctx: &mut ViewContext) { if self.galaxyctrl_installing || cli_install::is_galaxyctrl_installed() { return; } self.galaxyctrl_installing = true; ctx.notify(); let window_id = ctx.window_id(); ctx.spawn( async { cli_install::install_galaxyctrl() }, move |view, result, ctx| { view.galaxyctrl_installing = false; match result { Ok(()) => { let command_name = ChannelState::channel().galaxyctrl_command_name(); let message = format!( "Galaxy Control CLI installed. You can now run '{command_name}' from any terminal." ); ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| { toast_stack.add_ephemeral_toast( DismissibleToast::success(message), window_id, ctx, ); }); } Err(error) => { let message = format!("Failed to install Galaxy Control CLI: {error}"); log::warn!("{message}"); ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| { toast_stack.add_persistent_toast( DismissibleToast::error(message), window_id, ctx, ); }); } } ctx.notify(); }, ); } } impl Entity for ScriptingSettingsPageView { type Event = (); } impl TypedActionView for ScriptingSettingsPageView { type Action = ScriptingSettingsPageAction; fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext) { match action { ScriptingSettingsPageAction::SetLocalControlMode(mode) => { LocalControlSettings::handle(ctx).update(ctx, |settings, ctx| { report_if_error!(settings.local_control_mode.set_value(*mode, ctx)); }); ctx.notify(); } #[cfg(target_os = "macos")] ScriptingSettingsPageAction::InstallGalaxyControlCli => self.install_galaxyctrl(ctx), } } } impl View for ScriptingSettingsPageView { fn ui_name() -> &'static str { "ScriptingSettingsPage" } fn render(&self, app: &AppContext) -> Box { self.page.render(self, app) } } impl SettingsPageMeta for ScriptingSettingsPageView { fn section() -> SettingsSection { SettingsSection::Scripting } fn should_render(&self, _ctx: &AppContext) -> bool { cfg!(not(target_family = "wasm")) && FeatureFlag::GalaxyControlCli.is_enabled() } fn update_filter(&mut self, query: &str, ctx: &mut ViewContext) -> MatchData { self.page.update_filter(query, ctx) } fn scroll_to_widget(&mut self, widget_id: &'static str) { self.page.scroll_to_widget(widget_id) } fn clear_highlighted_widget(&mut self) { self.page.clear_highlighted_widget(); } } impl From> for SettingsPageViewHandle { fn from(view_handle: ViewHandle) -> Self { SettingsPageViewHandle::Scripting(view_handle) } } #[cfg(target_os = "macos")] #[derive(Default)] struct GalaxyControlCliInstallWidget { install_button_mouse_state: MouseStateHandle, } #[cfg(target_os = "macos")] impl SettingsWidget for GalaxyControlCliInstallWidget { type View = ScriptingSettingsPageView; fn search_terms(&self) -> &str { "galaxy control cli command galaxyctrl install scripting automation" } fn render( &self, view: &Self::View, appearance: &Appearance, _app: &AppContext, ) -> Box { let installed = cli_install::is_galaxyctrl_installed(); let disabled = view.galaxyctrl_installing || installed; let label = if view.galaxyctrl_installing { "Installing…" } else if installed { "Installed" } else { "Install" }; let mut button = appearance .ui_builder() .button( ButtonVariant::Secondary, self.install_button_mouse_state.clone(), ) .with_text_label(label.to_owned()); if disabled { button = button.disabled(); } let button = if disabled { button.build().finish() } else { button .build() .on_click(|ctx, _, _| { ctx.dispatch_typed_action(ScriptingSettingsPageAction::InstallGalaxyControlCli); }) .finish() }; render_body_item::( "Galaxy Control CLI".into(), None, LocalOnlyIconState::Hidden, ToggleState::Enabled, appearance, button, Some("Install the galaxyctrl command to control Galaxy from your terminal.".to_owned()), ) } } struct LocalControlModeWidget; impl SettingsWidget for LocalControlModeWidget { type View = ScriptingSettingsPageView; fn search_terms(&self) -> &str { "galaxy control scripting automation galaxyctrl local cli scripts access disabled enabled" } fn render( &self, view: &Self::View, appearance: &Appearance, app: &AppContext, ) -> Box { render_body_item::( "Local automation access".into(), None, LocalOnlyIconState::for_setting( LocalControlModeSetting::storage_key(), LocalControlModeSetting::sync_to_cloud(), &mut view.local_only_icon_tooltip_states.borrow_mut(), app, ), ToggleState::Enabled, appearance, ChildView::new(&view.local_control_mode_dropdown).finish(), Some( "Allow local scripts and agents running as your user account to control approved parts of Galaxy. Enable this only when you trust other software running on this computer." .to_owned(), ), ) } }