profile_selector.rs

  1use std::sync::Arc;
  2
  3use assistant_settings::{AgentProfile, AssistantSettings};
  4use fs::Fs;
  5use gpui::{prelude::*, Action, Entity, FocusHandle, Subscription, WeakEntity};
  6use indexmap::IndexMap;
  7use settings::{update_settings_file, Settings as _, SettingsStore};
  8use ui::{prelude::*, ContextMenu, ContextMenuEntry, PopoverMenu, PopoverMenuHandle, Tooltip};
  9use util::ResultExt as _;
 10
 11use crate::{ManageProfiles, ThreadStore, ToggleProfileSelector};
 12
 13pub struct ProfileSelector {
 14    profiles: IndexMap<Arc<str>, AgentProfile>,
 15    fs: Arc<dyn Fs>,
 16    thread_store: WeakEntity<ThreadStore>,
 17    focus_handle: FocusHandle,
 18    menu_handle: PopoverMenuHandle<ContextMenu>,
 19    _subscriptions: Vec<Subscription>,
 20}
 21
 22impl ProfileSelector {
 23    pub fn new(
 24        fs: Arc<dyn Fs>,
 25        thread_store: WeakEntity<ThreadStore>,
 26        focus_handle: FocusHandle,
 27        cx: &mut Context<Self>,
 28    ) -> Self {
 29        let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
 30            this.refresh_profiles(cx);
 31        });
 32
 33        let mut this = Self {
 34            profiles: IndexMap::default(),
 35            fs,
 36            thread_store,
 37            focus_handle,
 38            menu_handle: PopoverMenuHandle::default(),
 39            _subscriptions: vec![settings_subscription],
 40        };
 41        this.refresh_profiles(cx);
 42
 43        this
 44    }
 45
 46    pub fn menu_handle(&self) -> PopoverMenuHandle<ContextMenu> {
 47        self.menu_handle.clone()
 48    }
 49
 50    fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
 51        let settings = AssistantSettings::get_global(cx);
 52
 53        self.profiles = settings.profiles.clone();
 54    }
 55
 56    fn build_context_menu(
 57        &self,
 58        window: &mut Window,
 59        cx: &mut Context<Self>,
 60    ) -> Entity<ContextMenu> {
 61        ContextMenu::build(window, cx, |mut menu, _window, cx| {
 62            let settings = AssistantSettings::get_global(cx);
 63            let icon_position = IconPosition::Start;
 64
 65            menu = menu.header("Profiles");
 66            for (profile_id, profile) in self.profiles.clone() {
 67                menu = menu.toggleable_entry(
 68                    profile.name.clone(),
 69                    profile_id == settings.default_profile,
 70                    icon_position,
 71                    None,
 72                    {
 73                        let fs = self.fs.clone();
 74                        let thread_store = self.thread_store.clone();
 75                        move |_window, cx| {
 76                            update_settings_file::<AssistantSettings>(fs.clone(), cx, {
 77                                let profile_id = profile_id.clone();
 78                                move |settings, _cx| {
 79                                    settings.set_profile(profile_id.clone());
 80                                }
 81                            });
 82
 83                            thread_store
 84                                .update(cx, |this, cx| {
 85                                    this.load_profile_by_id(&profile_id, cx);
 86                                })
 87                                .log_err();
 88                        }
 89                    },
 90                );
 91            }
 92
 93            menu = menu.separator();
 94            menu = menu.item(
 95                ContextMenuEntry::new("Configure Profiles")
 96                    .icon(IconName::Pencil)
 97                    .icon_color(Color::Muted)
 98                    .handler(move |window, cx| {
 99                        window.dispatch_action(ManageProfiles.boxed_clone(), cx);
100                    }),
101            );
102
103            menu
104        })
105    }
106}
107
108impl Render for ProfileSelector {
109    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
110        let settings = AssistantSettings::get_global(cx);
111        let profile = settings
112            .profiles
113            .get(&settings.default_profile)
114            .map(|profile| profile.name.clone())
115            .unwrap_or_else(|| "Unknown".into());
116
117        let this = cx.entity().clone();
118        let focus_handle = self.focus_handle.clone();
119        PopoverMenu::new("profile-selector")
120            .menu(move |window, cx| {
121                Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
122            })
123            .trigger_with_tooltip(
124                Button::new("profile-selector-button", profile)
125                    .style(ButtonStyle::Filled)
126                    .label_size(LabelSize::Small),
127                move |window, cx| {
128                    Tooltip::for_action_in(
129                        "Change Profile",
130                        &ToggleProfileSelector,
131                        &focus_handle,
132                        window,
133                        cx,
134                    )
135                },
136            )
137            .anchor(gpui::Corner::BottomLeft)
138            .with_handle(self.menu_handle.clone())
139    }
140}