1use std::sync::Arc;
2
3use assistant_settings::{AgentProfile, AssistantSettings};
4use assistant_tool::{ToolSource, ToolWorkingSet};
5use gpui::{Entity, Subscription};
6use indexmap::IndexMap;
7use scripting_tool::ScriptingTool;
8use settings::{Settings as _, SettingsStore};
9use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
10
11pub struct ToolSelector {
12 profiles: IndexMap<Arc<str>, AgentProfile>,
13 tools: Arc<ToolWorkingSet>,
14 _subscriptions: Vec<Subscription>,
15}
16
17impl ToolSelector {
18 pub fn new(tools: Arc<ToolWorkingSet>, cx: &mut Context<Self>) -> Self {
19 let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
20 this.refresh_profiles(cx);
21 });
22
23 let mut this = Self {
24 profiles: IndexMap::default(),
25 tools,
26 _subscriptions: vec![settings_subscription],
27 };
28 this.refresh_profiles(cx);
29
30 this
31 }
32
33 fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
34 let settings = AssistantSettings::get_global(cx);
35
36 self.profiles = settings.profiles.clone();
37 }
38
39 fn build_context_menu(
40 &self,
41 window: &mut Window,
42 cx: &mut Context<Self>,
43 ) -> Entity<ContextMenu> {
44 let profiles = self.profiles.clone();
45 let tool_set = self.tools.clone();
46 ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
47 let icon_position = IconPosition::End;
48
49 menu = menu.header("Profiles");
50 for (_id, profile) in profiles.clone() {
51 menu = menu.toggleable_entry(profile.name.clone(), false, icon_position, None, {
52 let tools = tool_set.clone();
53 move |_window, cx| {
54 tools.disable_source(ToolSource::Native, cx);
55 tools.disable_scripting_tool();
56 tools.enable(
57 ToolSource::Native,
58 &profile
59 .tools
60 .iter()
61 .filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
62 .collect::<Vec<_>>(),
63 );
64
65 if profile.tools.contains_key(ScriptingTool::NAME) {
66 tools.enable_scripting_tool();
67 }
68 }
69 });
70 }
71
72 menu = menu.separator();
73
74 let tools_by_source = tool_set.tools_by_source(cx);
75
76 let all_tools_enabled = tool_set.are_all_tools_enabled();
77 menu = menu.toggleable_entry("All Tools", all_tools_enabled, icon_position, None, {
78 let tools = tool_set.clone();
79 move |_window, cx| {
80 if all_tools_enabled {
81 tools.disable_all_tools(cx);
82 } else {
83 tools.enable_all_tools();
84 }
85 }
86 });
87
88 for (source, tools) in tools_by_source {
89 let mut tools = tools
90 .into_iter()
91 .map(|tool| {
92 let source = tool.source();
93 let name = tool.name().into();
94 let is_enabled = tool_set.is_enabled(&source, &name);
95
96 (source, name, is_enabled)
97 })
98 .collect::<Vec<_>>();
99
100 if ToolSource::Native == source {
101 tools.push((
102 ToolSource::Native,
103 ScriptingTool::NAME.into(),
104 tool_set.is_scripting_tool_enabled(),
105 ));
106 tools.sort_by(|(_, name_a, _), (_, name_b, _)| name_a.cmp(name_b));
107 }
108
109 menu = match &source {
110 ToolSource::Native => menu.separator().header("Zed Tools"),
111 ToolSource::ContextServer { id } => {
112 let all_tools_from_source_enabled =
113 tool_set.are_all_tools_from_source_enabled(&source);
114
115 menu.separator().header(id).toggleable_entry(
116 "All Tools",
117 all_tools_from_source_enabled,
118 icon_position,
119 None,
120 {
121 let tools = tool_set.clone();
122 let source = source.clone();
123 move |_window, cx| {
124 if all_tools_from_source_enabled {
125 tools.disable_source(source.clone(), cx);
126 } else {
127 tools.enable_source(&source);
128 }
129 }
130 },
131 )
132 }
133 };
134
135 for (source, name, is_enabled) in tools {
136 menu = menu.toggleable_entry(name.clone(), is_enabled, icon_position, None, {
137 let tools = tool_set.clone();
138 move |_window, _cx| {
139 if name.as_ref() == ScriptingTool::NAME {
140 if is_enabled {
141 tools.disable_scripting_tool();
142 } else {
143 tools.enable_scripting_tool();
144 }
145 } else {
146 if is_enabled {
147 tools.disable(source.clone(), &[name.clone()]);
148 } else {
149 tools.enable(source.clone(), &[name.clone()]);
150 }
151 }
152 }
153 });
154 }
155 }
156
157 menu
158 })
159 }
160}
161
162impl Render for ToolSelector {
163 fn render(&mut self, _window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
164 let this = cx.entity().clone();
165 PopoverMenu::new("tool-selector")
166 .menu(move |window, cx| {
167 Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
168 })
169 .trigger_with_tooltip(
170 IconButton::new("tool-selector-button", IconName::SettingsAlt)
171 .icon_size(IconSize::Small)
172 .icon_color(Color::Muted),
173 Tooltip::text("Customize Tools"),
174 )
175 .anchor(gpui::Corner::BottomLeft)
176 }
177}