1use std::sync::Arc;
2
3use assistant_settings::AssistantSettings;
4use assistant_tool::ToolWorkingSet;
5use fs::Fs;
6use gpui::{prelude::*, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription};
7use settings::Settings as _;
8use ui::{prelude::*, ListItem, ListItemSpacing, Navigable, NavigableEntry};
9use workspace::{ModalView, Workspace};
10
11use crate::assistant_configuration::profile_picker::{ProfilePicker, ProfilePickerDelegate};
12use crate::assistant_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
13use crate::{AssistantPanel, ManageProfiles};
14
15enum Mode {
16 ChooseProfile {
17 profile_picker: Entity<ProfilePicker>,
18 _subscription: Subscription,
19 },
20 ViewProfile(ViewProfileMode),
21 ConfigureTools {
22 tool_picker: Entity<ToolPicker>,
23 _subscription: Subscription,
24 },
25}
26
27impl Mode {
28 pub fn choose_profile(window: &mut Window, cx: &mut Context<ManageProfilesModal>) -> Self {
29 let this = cx.entity();
30
31 let profile_picker = cx.new(|cx| {
32 let delegate = ProfilePickerDelegate::new(
33 move |profile_id, window, cx| {
34 this.update(cx, |this, cx| {
35 this.view_profile(profile_id.clone(), window, cx);
36 })
37 },
38 cx,
39 );
40 ProfilePicker::new(delegate, window, cx)
41 });
42 let dismiss_subscription = cx.subscribe_in(
43 &profile_picker,
44 window,
45 |_this, _profile_picker, _: &DismissEvent, _window, cx| {
46 cx.emit(DismissEvent);
47 },
48 );
49
50 Self::ChooseProfile {
51 profile_picker,
52 _subscription: dismiss_subscription,
53 }
54 }
55}
56
57#[derive(Clone)]
58pub struct ViewProfileMode {
59 profile_id: Arc<str>,
60 configure_tools: NavigableEntry,
61}
62
63pub struct ManageProfilesModal {
64 fs: Arc<dyn Fs>,
65 tools: Arc<ToolWorkingSet>,
66 focus_handle: FocusHandle,
67 mode: Mode,
68}
69
70impl ManageProfilesModal {
71 pub fn register(
72 workspace: &mut Workspace,
73 _window: Option<&mut Window>,
74 _cx: &mut Context<Workspace>,
75 ) {
76 workspace.register_action(|workspace, _: &ManageProfiles, window, cx| {
77 if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
78 let fs = workspace.app_state().fs.clone();
79 let thread_store = panel.read(cx).thread_store().read(cx);
80 let tools = thread_store.tools();
81 workspace.toggle_modal(window, cx, |window, cx| Self::new(fs, tools, window, cx))
82 }
83 });
84 }
85
86 pub fn new(
87 fs: Arc<dyn Fs>,
88 tools: Arc<ToolWorkingSet>,
89 window: &mut Window,
90 cx: &mut Context<Self>,
91 ) -> Self {
92 let focus_handle = cx.focus_handle();
93
94 Self {
95 fs,
96 tools,
97 focus_handle,
98 mode: Mode::choose_profile(window, cx),
99 }
100 }
101
102 fn choose_profile(&mut self, window: &mut Window, cx: &mut Context<Self>) {
103 self.mode = Mode::choose_profile(window, cx);
104 self.focus_handle(cx).focus(window);
105 }
106
107 pub fn view_profile(
108 &mut self,
109 profile_id: Arc<str>,
110 window: &mut Window,
111 cx: &mut Context<Self>,
112 ) {
113 self.mode = Mode::ViewProfile(ViewProfileMode {
114 profile_id,
115 configure_tools: NavigableEntry::focusable(cx),
116 });
117 self.focus_handle(cx).focus(window);
118 }
119
120 fn configure_tools(
121 &mut self,
122 profile_id: Arc<str>,
123 window: &mut Window,
124 cx: &mut Context<Self>,
125 ) {
126 let settings = AssistantSettings::get_global(cx);
127 let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
128 return;
129 };
130
131 let tool_picker = cx.new(|cx| {
132 let delegate = ToolPickerDelegate::new(
133 self.fs.clone(),
134 self.tools.clone(),
135 profile_id.clone(),
136 profile,
137 cx,
138 );
139 ToolPicker::new(delegate, window, cx)
140 });
141 let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
142 let profile_id = profile_id.clone();
143 move |this, _tool_picker, _: &DismissEvent, window, cx| {
144 this.view_profile(profile_id.clone(), window, cx);
145 }
146 });
147
148 self.mode = Mode::ConfigureTools {
149 tool_picker,
150 _subscription: dismiss_subscription,
151 };
152 self.focus_handle(cx).focus(window);
153 }
154
155 fn confirm(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
156
157 fn cancel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
158 match &self.mode {
159 Mode::ChooseProfile { .. } => {}
160 Mode::ViewProfile(_) => self.choose_profile(window, cx),
161 Mode::ConfigureTools { .. } => {}
162 }
163 }
164}
165
166impl ModalView for ManageProfilesModal {}
167
168impl Focusable for ManageProfilesModal {
169 fn focus_handle(&self, cx: &App) -> FocusHandle {
170 match &self.mode {
171 Mode::ChooseProfile { profile_picker, .. } => profile_picker.focus_handle(cx),
172 Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx),
173 Mode::ViewProfile(_) => self.focus_handle.clone(),
174 }
175 }
176}
177
178impl EventEmitter<DismissEvent> for ManageProfilesModal {}
179
180impl ManageProfilesModal {
181 fn render_view_profile(
182 &mut self,
183 mode: ViewProfileMode,
184 window: &mut Window,
185 cx: &mut Context<Self>,
186 ) -> impl IntoElement {
187 Navigable::new(
188 div()
189 .track_focus(&self.focus_handle(cx))
190 .size_full()
191 .child(
192 v_flex().child(
193 div()
194 .id("configure-tools")
195 .track_focus(&mode.configure_tools.focus_handle)
196 .on_action({
197 let profile_id = mode.profile_id.clone();
198 cx.listener(move |this, _: &menu::Confirm, window, cx| {
199 this.configure_tools(profile_id.clone(), window, cx);
200 })
201 })
202 .child(
203 ListItem::new("configure-tools")
204 .toggle_state(
205 mode.configure_tools
206 .focus_handle
207 .contains_focused(window, cx),
208 )
209 .inset(true)
210 .spacing(ListItemSpacing::Sparse)
211 .start_slot(Icon::new(IconName::Cog))
212 .child(Label::new("Configure Tools"))
213 .on_click({
214 let profile_id = mode.profile_id.clone();
215 cx.listener(move |this, _, window, cx| {
216 this.configure_tools(profile_id.clone(), window, cx);
217 })
218 }),
219 ),
220 ),
221 )
222 .into_any_element(),
223 )
224 .entry(mode.configure_tools)
225 }
226}
227
228impl Render for ManageProfilesModal {
229 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
230 div()
231 .elevation_3(cx)
232 .w(rems(34.))
233 .key_context("ManageProfilesModal")
234 .on_action(cx.listener(|this, _: &menu::Cancel, window, cx| this.cancel(window, cx)))
235 .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| this.confirm(window, cx)))
236 .capture_any_mouse_down(cx.listener(|this, _, window, cx| {
237 this.focus_handle(cx).focus(window);
238 }))
239 .on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
240 .child(match &self.mode {
241 Mode::ChooseProfile { profile_picker, .. } => {
242 profile_picker.clone().into_any_element()
243 }
244 Mode::ViewProfile(mode) => self
245 .render_view_profile(mode.clone(), window, cx)
246 .into_any_element(),
247 Mode::ConfigureTools { tool_picker, .. } => tool_picker.clone().into_any_element(),
248 })
249 }
250}