manage_profiles_modal.rs

  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}