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