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(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}