profile_selector.rs

  1use std::sync::Arc;
  2
  3use assistant_settings::{AgentProfile, AssistantSettings};
  4use fs::Fs;
  5use gpui::{Action, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
  6use indexmap::IndexMap;
  7use settings::{Settings as _, SettingsStore, update_settings_file};
  8use ui::{
  9    ButtonLike, ContextMenu, ContextMenuEntry, KeyBinding, PopoverMenu, PopoverMenuHandle,
 10    prelude::*,
 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.header("Customize Current Profile");
 98            menu = menu.item(ContextMenuEntry::new("Tools…").handler({
 99                let profile_id = settings.default_profile.clone();
100                move |window, cx| {
101                    window.dispatch_action(
102                        ManageProfiles::customize_tools(profile_id.clone()).boxed_clone(),
103                        cx,
104                    );
105                }
106            }));
107
108            menu = menu.separator();
109            menu = menu.item(ContextMenuEntry::new("Configure Profiles…").handler(
110                move |window, cx| {
111                    window.dispatch_action(ManageProfiles::default().boxed_clone(), cx);
112                },
113            ));
114
115            menu
116        })
117    }
118}
119
120impl Render for ProfileSelector {
121    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
122        let settings = AssistantSettings::get_global(cx);
123        let profile_id = &settings.default_profile;
124        let profile = settings.profiles.get(profile_id);
125
126        let selected_profile = profile
127            .map(|profile| profile.name.clone())
128            .unwrap_or_else(|| "Unknown".into());
129
130        let icon = match profile_id.as_ref() {
131            "write" => IconName::Pencil,
132            "ask" => IconName::MessageBubbles,
133            _ => IconName::UserRoundPen,
134        };
135
136        let this = cx.entity().clone();
137        let focus_handle = self.focus_handle.clone();
138        PopoverMenu::new("profile-selector")
139            .menu(move |window, cx| {
140                Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
141            })
142            .trigger(
143                ButtonLike::new("profile-selector-button").child(
144                    h_flex()
145                        .gap_1()
146                        .child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted))
147                        .child(
148                            Label::new(selected_profile)
149                                .size(LabelSize::Small)
150                                .color(Color::Muted),
151                        )
152                        .child(
153                            Icon::new(IconName::ChevronDown)
154                                .size(IconSize::XSmall)
155                                .color(Color::Muted),
156                        )
157                        .child(div().opacity(0.5).children({
158                            let focus_handle = focus_handle.clone();
159                            KeyBinding::for_action_in(
160                                &ToggleProfileSelector,
161                                &focus_handle,
162                                window,
163                                cx,
164                            )
165                            .map(|kb| kb.size(rems_from_px(10.)))
166                        })),
167                ),
168            )
169            .anchor(gpui::Corner::BottomLeft)
170            .with_handle(self.menu_handle.clone())
171    }
172}