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}