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::{
  9    prelude::*, ButtonLike, ContextMenu, ContextMenuEntry, KeyBinding, PopoverMenu,
 10    PopoverMenuHandle,
 11};
 12use util::ResultExt as _;
 13
 14use crate::{ManageProfiles, ThreadStore, ToggleProfileSelector};
 15
 16pub struct ProfileSelector {
 17    profiles: IndexMap<Arc<str>, AgentProfile>,
 18    fs: Arc<dyn Fs>,
 19    thread_store: WeakEntity<ThreadStore>,
 20    focus_handle: FocusHandle,
 21    menu_handle: PopoverMenuHandle<ContextMenu>,
 22    _subscriptions: Vec<Subscription>,
 23}
 24
 25impl ProfileSelector {
 26    pub fn new(
 27        fs: Arc<dyn Fs>,
 28        thread_store: WeakEntity<ThreadStore>,
 29        focus_handle: FocusHandle,
 30        cx: &mut Context<Self>,
 31    ) -> Self {
 32        let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
 33            this.refresh_profiles(cx);
 34        });
 35
 36        let mut this = Self {
 37            profiles: IndexMap::default(),
 38            fs,
 39            thread_store,
 40            focus_handle,
 41            menu_handle: PopoverMenuHandle::default(),
 42            _subscriptions: vec![settings_subscription],
 43        };
 44        this.refresh_profiles(cx);
 45
 46        this
 47    }
 48
 49    pub fn menu_handle(&self) -> PopoverMenuHandle<ContextMenu> {
 50        self.menu_handle.clone()
 51    }
 52
 53    fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
 54        let settings = AssistantSettings::get_global(cx);
 55
 56        self.profiles = settings.profiles.clone();
 57    }
 58
 59    fn build_context_menu(
 60        &self,
 61        window: &mut Window,
 62        cx: &mut Context<Self>,
 63    ) -> Entity<ContextMenu> {
 64        ContextMenu::build(window, cx, |mut menu, _window, cx| {
 65            let settings = AssistantSettings::get_global(cx);
 66            let icon_position = IconPosition::End;
 67
 68            menu = menu.header("Profiles");
 69            for (profile_id, profile) in self.profiles.clone() {
 70                menu = menu.toggleable_entry(
 71                    profile.name.clone(),
 72                    profile_id == settings.default_profile,
 73                    icon_position,
 74                    None,
 75                    {
 76                        let fs = self.fs.clone();
 77                        let thread_store = self.thread_store.clone();
 78                        move |_window, cx| {
 79                            update_settings_file::<AssistantSettings>(fs.clone(), cx, {
 80                                let profile_id = profile_id.clone();
 81                                move |settings, _cx| {
 82                                    settings.set_profile(profile_id.clone());
 83                                }
 84                            });
 85
 86                            thread_store
 87                                .update(cx, |this, cx| {
 88                                    this.load_profile_by_id(&profile_id, cx);
 89                                })
 90                                .log_err();
 91                        }
 92                    },
 93                );
 94            }
 95
 96            menu = menu.separator();
 97            menu = menu.item(ContextMenuEntry::new("Configure Profiles").handler(
 98                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_id = &settings.default_profile;
112        let profile = settings.profiles.get(profile_id);
113
114        let selected_profile = profile
115            .map(|profile| profile.name.clone())
116            .unwrap_or_else(|| "Unknown".into());
117
118        let icon = match profile_id.as_ref() {
119            "write" => IconName::Pencil,
120            "ask" => IconName::MessageBubbles,
121            _ => IconName::UserRoundPen,
122        };
123
124        let this = cx.entity().clone();
125        let focus_handle = self.focus_handle.clone();
126        PopoverMenu::new("profile-selector")
127            .menu(move |window, cx| {
128                Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
129            })
130            .trigger(
131                ButtonLike::new("profile-selector-button").child(
132                    h_flex()
133                        .gap_1()
134                        .child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted))
135                        .child(
136                            Label::new(selected_profile)
137                                .size(LabelSize::Small)
138                                .color(Color::Muted),
139                        )
140                        .child(
141                            Icon::new(IconName::ChevronDown)
142                                .size(IconSize::XSmall)
143                                .color(Color::Muted),
144                        )
145                        .child(div().opacity(0.5).children({
146                            let focus_handle = focus_handle.clone();
147                            KeyBinding::for_action_in(
148                                &ToggleProfileSelector,
149                                &focus_handle,
150                                window,
151                                cx,
152                            )
153                            .map(|kb| kb.size(rems_from_px(10.)))
154                        })),
155                ),
156            )
157            .anchor(gpui::Corner::BottomLeft)
158            .with_handle(self.menu_handle.clone())
159    }
160}