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