manage_profiles_modal.rs

  1mod profile_modal_header;
  2
  3use std::sync::Arc;
  4
  5use agent::ContextServerRegistry;
  6use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
  7use editor::Editor;
  8use fs::Fs;
  9use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*};
 10use language_model::{LanguageModel, LanguageModelRegistry};
 11use settings::{
 12    LanguageModelProviderSetting, LanguageModelSelection, Settings as _, update_settings_file,
 13};
 14use ui::{
 15    KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*,
 16};
 17use workspace::{ModalView, Workspace};
 18
 19use crate::agent_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
 20use crate::agent_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
 21use crate::language_model_selector::{LanguageModelSelector, language_model_selector};
 22use crate::{AgentPanel, ManageProfiles};
 23
 24enum Mode {
 25    ChooseProfile(ChooseProfileMode),
 26    NewProfile(NewProfileMode),
 27    ViewProfile(ViewProfileMode),
 28    ConfigureTools {
 29        profile_id: AgentProfileId,
 30        tool_picker: Entity<ToolPicker>,
 31        _subscription: Subscription,
 32    },
 33    ConfigureMcps {
 34        profile_id: AgentProfileId,
 35        tool_picker: Entity<ToolPicker>,
 36        _subscription: Subscription,
 37    },
 38    ConfigureDefaultModel {
 39        profile_id: AgentProfileId,
 40        model_picker: Entity<LanguageModelSelector>,
 41        _subscription: Subscription,
 42    },
 43}
 44
 45impl Mode {
 46    pub fn choose_profile(_window: &mut Window, cx: &mut Context<ManageProfilesModal>) -> Self {
 47        let settings = AgentSettings::get_global(cx);
 48
 49        let mut builtin_profiles = Vec::new();
 50        let mut custom_profiles = Vec::new();
 51
 52        for (profile_id, profile) in settings.profiles.iter() {
 53            let entry = ProfileEntry {
 54                id: profile_id.clone(),
 55                name: profile.name.clone(),
 56                navigation: NavigableEntry::focusable(cx),
 57            };
 58            if builtin_profiles::is_builtin(profile_id) {
 59                builtin_profiles.push(entry);
 60            } else {
 61                custom_profiles.push(entry);
 62            }
 63        }
 64
 65        builtin_profiles.sort_unstable_by(|a, b| a.name.cmp(&b.name));
 66        custom_profiles.sort_unstable_by(|a, b| a.name.cmp(&b.name));
 67
 68        Self::ChooseProfile(ChooseProfileMode {
 69            builtin_profiles,
 70            custom_profiles,
 71            add_new_profile: NavigableEntry::focusable(cx),
 72        })
 73    }
 74}
 75
 76#[derive(Clone)]
 77struct ProfileEntry {
 78    pub id: AgentProfileId,
 79    pub name: SharedString,
 80    pub navigation: NavigableEntry,
 81}
 82
 83#[derive(Clone)]
 84pub struct ChooseProfileMode {
 85    builtin_profiles: Vec<ProfileEntry>,
 86    custom_profiles: Vec<ProfileEntry>,
 87    add_new_profile: NavigableEntry,
 88}
 89
 90#[derive(Clone)]
 91pub struct ViewProfileMode {
 92    profile_id: AgentProfileId,
 93    fork_profile: NavigableEntry,
 94    configure_default_model: NavigableEntry,
 95    configure_tools: NavigableEntry,
 96    configure_mcps: NavigableEntry,
 97    cancel_item: NavigableEntry,
 98}
 99
100#[derive(Clone)]
101pub struct NewProfileMode {
102    name_editor: Entity<Editor>,
103    base_profile_id: Option<AgentProfileId>,
104}
105
106pub struct ManageProfilesModal {
107    fs: Arc<dyn Fs>,
108    context_server_registry: Entity<ContextServerRegistry>,
109    active_model: Option<Arc<dyn LanguageModel>>,
110    focus_handle: FocusHandle,
111    mode: Mode,
112}
113
114impl ManageProfilesModal {
115    pub fn register(
116        workspace: &mut Workspace,
117        _window: Option<&mut Window>,
118        _cx: &mut Context<Workspace>,
119    ) {
120        workspace.register_action(|workspace, action: &ManageProfiles, window, cx| {
121            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
122                let fs = workspace.app_state().fs.clone();
123                let active_model = panel
124                    .read(cx)
125                    .active_native_agent_thread(cx)
126                    .and_then(|thread| thread.read(cx).model().cloned());
127
128                let context_server_registry = panel.read(cx).context_server_registry().clone();
129                workspace.toggle_modal(window, cx, |window, cx| {
130                    let mut this = Self::new(fs, active_model, context_server_registry, window, cx);
131
132                    if let Some(profile_id) = action.customize_tools.clone() {
133                        this.configure_builtin_tools(profile_id, window, cx);
134                    }
135
136                    this
137                })
138            }
139        });
140    }
141
142    pub fn new(
143        fs: Arc<dyn Fs>,
144        active_model: Option<Arc<dyn LanguageModel>>,
145        context_server_registry: Entity<ContextServerRegistry>,
146        window: &mut Window,
147        cx: &mut Context<Self>,
148    ) -> Self {
149        let focus_handle = cx.focus_handle();
150
151        Self {
152            fs,
153            active_model,
154            context_server_registry,
155            focus_handle,
156            mode: Mode::choose_profile(window, cx),
157        }
158    }
159
160    fn choose_profile(&mut self, window: &mut Window, cx: &mut Context<Self>) {
161        self.mode = Mode::choose_profile(window, cx);
162        self.focus_handle(cx).focus(window);
163    }
164
165    fn new_profile(
166        &mut self,
167        base_profile_id: Option<AgentProfileId>,
168        window: &mut Window,
169        cx: &mut Context<Self>,
170    ) {
171        let name_editor = cx.new(|cx| Editor::single_line(window, cx));
172        name_editor.update(cx, |editor, cx| {
173            editor.set_placeholder_text("Profile name", window, cx);
174        });
175
176        self.mode = Mode::NewProfile(NewProfileMode {
177            name_editor,
178            base_profile_id,
179        });
180        self.focus_handle(cx).focus(window);
181    }
182
183    pub fn view_profile(
184        &mut self,
185        profile_id: AgentProfileId,
186        window: &mut Window,
187        cx: &mut Context<Self>,
188    ) {
189        self.mode = Mode::ViewProfile(ViewProfileMode {
190            profile_id,
191            fork_profile: NavigableEntry::focusable(cx),
192            configure_default_model: NavigableEntry::focusable(cx),
193            configure_tools: NavigableEntry::focusable(cx),
194            configure_mcps: NavigableEntry::focusable(cx),
195            cancel_item: NavigableEntry::focusable(cx),
196        });
197        self.focus_handle(cx).focus(window);
198    }
199
200    fn configure_default_model(
201        &mut self,
202        profile_id: AgentProfileId,
203        window: &mut Window,
204        cx: &mut Context<Self>,
205    ) {
206        let fs = self.fs.clone();
207        let profile_id_for_closure = profile_id.clone();
208
209        let model_picker = cx.new(|cx| {
210            let fs = fs.clone();
211            let profile_id = profile_id_for_closure.clone();
212
213            language_model_selector(
214                {
215                    let profile_id = profile_id.clone();
216                    move |cx| {
217                        let settings = AgentSettings::get_global(cx);
218
219                        settings
220                            .profiles
221                            .get(&profile_id)
222                            .and_then(|profile| profile.default_model.as_ref())
223                            .and_then(|selection| {
224                                let registry = LanguageModelRegistry::read_global(cx);
225                                let provider_id = language_model::LanguageModelProviderId(
226                                    gpui::SharedString::from(selection.provider.0.clone()),
227                                );
228                                let provider = registry.provider(&provider_id)?;
229                                let model = provider
230                                    .provided_models(cx)
231                                    .iter()
232                                    .find(|m| m.id().0 == selection.model.as_str())?
233                                    .clone();
234                                Some(language_model::ConfiguredModel { provider, model })
235                            })
236                    }
237                },
238                move |model, cx| {
239                    let provider = model.provider_id().0.to_string();
240                    let model_id = model.id().0.to_string();
241                    let profile_id = profile_id.clone();
242
243                    update_settings_file(fs.clone(), cx, move |settings, _cx| {
244                        let agent_settings = settings.agent.get_or_insert_default();
245                        if let Some(profiles) = agent_settings.profiles.as_mut() {
246                            if let Some(profile) = profiles.get_mut(profile_id.0.as_ref()) {
247                                profile.default_model = Some(LanguageModelSelection {
248                                    provider: LanguageModelProviderSetting(provider.clone()),
249                                    model: model_id.clone(),
250                                });
251                            }
252                        }
253                    });
254                },
255                false, // Do not use popover styles for the model picker
256                window,
257                cx,
258            )
259            .modal(false)
260        });
261
262        let dismiss_subscription = cx.subscribe_in(&model_picker, window, {
263            let profile_id = profile_id.clone();
264            move |this, _picker, _: &DismissEvent, window, cx| {
265                this.view_profile(profile_id.clone(), window, cx);
266            }
267        });
268
269        self.mode = Mode::ConfigureDefaultModel {
270            profile_id,
271            model_picker,
272            _subscription: dismiss_subscription,
273        };
274        self.focus_handle(cx).focus(window);
275    }
276
277    fn configure_mcp_tools(
278        &mut self,
279        profile_id: AgentProfileId,
280        window: &mut Window,
281        cx: &mut Context<Self>,
282    ) {
283        let settings = AgentSettings::get_global(cx);
284        let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
285            return;
286        };
287
288        let tool_picker = cx.new(|cx| {
289            let delegate = ToolPickerDelegate::mcp_tools(
290                &self.context_server_registry,
291                self.fs.clone(),
292                profile_id.clone(),
293                profile,
294                cx,
295            );
296            ToolPicker::mcp_tools(delegate, window, cx)
297        });
298        let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
299            let profile_id = profile_id.clone();
300            move |this, _tool_picker, _: &DismissEvent, window, cx| {
301                this.view_profile(profile_id.clone(), window, cx);
302            }
303        });
304
305        self.mode = Mode::ConfigureMcps {
306            profile_id,
307            tool_picker,
308            _subscription: dismiss_subscription,
309        };
310        self.focus_handle(cx).focus(window);
311    }
312
313    fn configure_builtin_tools(
314        &mut self,
315        profile_id: AgentProfileId,
316        window: &mut Window,
317        cx: &mut Context<Self>,
318    ) {
319        let settings = AgentSettings::get_global(cx);
320        let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
321            return;
322        };
323
324        let tool_picker = cx.new(|cx| {
325            let delegate = ToolPickerDelegate::builtin_tools(
326                //todo: This causes the web search tool to show up even it only works when using zed hosted models
327                agent::supported_built_in_tool_names(
328                    self.active_model.as_ref().map(|model| model.provider_id()),
329                )
330                .map(|s| s.into())
331                .collect::<Vec<_>>(),
332                self.fs.clone(),
333                profile_id.clone(),
334                profile,
335                cx,
336            );
337            ToolPicker::builtin_tools(delegate, window, cx)
338        });
339        let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
340            let profile_id = profile_id.clone();
341            move |this, _tool_picker, _: &DismissEvent, window, cx| {
342                this.view_profile(profile_id.clone(), window, cx);
343            }
344        });
345
346        self.mode = Mode::ConfigureTools {
347            profile_id,
348            tool_picker,
349            _subscription: dismiss_subscription,
350        };
351        self.focus_handle(cx).focus(window);
352    }
353
354    fn confirm(&mut self, window: &mut Window, cx: &mut Context<Self>) {
355        match &self.mode {
356            Mode::ChooseProfile { .. } => {}
357            Mode::NewProfile(mode) => {
358                let name = mode.name_editor.read(cx).text(cx);
359
360                let profile_id =
361                    AgentProfile::create(name, mode.base_profile_id.clone(), self.fs.clone(), cx);
362                self.view_profile(profile_id, window, cx);
363            }
364            Mode::ViewProfile(_) => {}
365            Mode::ConfigureTools { .. } => {}
366            Mode::ConfigureMcps { .. } => {}
367            Mode::ConfigureDefaultModel { .. } => {}
368        }
369    }
370
371    fn cancel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
372        match &self.mode {
373            Mode::ChooseProfile { .. } => {
374                cx.emit(DismissEvent);
375            }
376            Mode::NewProfile(mode) => {
377                if let Some(profile_id) = mode.base_profile_id.clone() {
378                    self.view_profile(profile_id, window, cx);
379                } else {
380                    self.choose_profile(window, cx);
381                }
382            }
383            Mode::ViewProfile(_) => self.choose_profile(window, cx),
384            Mode::ConfigureTools { profile_id, .. } => {
385                self.view_profile(profile_id.clone(), window, cx)
386            }
387            Mode::ConfigureMcps { profile_id, .. } => {
388                self.view_profile(profile_id.clone(), window, cx)
389            }
390            Mode::ConfigureDefaultModel { profile_id, .. } => {
391                self.view_profile(profile_id.clone(), window, cx)
392            }
393        }
394    }
395}
396
397impl ModalView for ManageProfilesModal {}
398
399impl Focusable for ManageProfilesModal {
400    fn focus_handle(&self, cx: &App) -> FocusHandle {
401        match &self.mode {
402            Mode::ChooseProfile(_) => self.focus_handle.clone(),
403            Mode::NewProfile(mode) => mode.name_editor.focus_handle(cx),
404            Mode::ViewProfile(_) => self.focus_handle.clone(),
405            Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx),
406            Mode::ConfigureMcps { tool_picker, .. } => tool_picker.focus_handle(cx),
407            Mode::ConfigureDefaultModel { model_picker, .. } => model_picker.focus_handle(cx),
408        }
409    }
410}
411
412impl EventEmitter<DismissEvent> for ManageProfilesModal {}
413
414impl ManageProfilesModal {
415    fn render_profile(
416        &self,
417        profile: &ProfileEntry,
418        window: &mut Window,
419        cx: &mut Context<Self>,
420    ) -> impl IntoElement + use<> {
421        let is_focused = profile.navigation.focus_handle.contains_focused(window, cx);
422
423        div()
424            .id(SharedString::from(format!("profile-{}", profile.id)))
425            .track_focus(&profile.navigation.focus_handle)
426            .on_action({
427                let profile_id = profile.id.clone();
428                cx.listener(move |this, _: &menu::Confirm, window, cx| {
429                    this.view_profile(profile_id.clone(), window, cx);
430                })
431            })
432            .child(
433                ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
434                    .toggle_state(is_focused)
435                    .inset(true)
436                    .spacing(ListItemSpacing::Sparse)
437                    .child(Label::new(profile.name.clone()))
438                    .when(is_focused, |this| {
439                        this.end_slot(
440                            h_flex()
441                                .gap_1()
442                                .child(
443                                    Label::new("Customize")
444                                        .size(LabelSize::Small)
445                                        .color(Color::Muted),
446                                )
447                                .child(KeyBinding::for_action_in(
448                                    &menu::Confirm,
449                                    &self.focus_handle,
450                                    cx,
451                                )),
452                        )
453                    })
454                    .on_click({
455                        let profile_id = profile.id.clone();
456                        cx.listener(move |this, _, window, cx| {
457                            this.view_profile(profile_id.clone(), window, cx);
458                        })
459                    }),
460            )
461    }
462
463    fn render_choose_profile(
464        &mut self,
465        mode: ChooseProfileMode,
466        window: &mut Window,
467        cx: &mut Context<Self>,
468    ) -> impl IntoElement {
469        Navigable::new(
470            div()
471                .track_focus(&self.focus_handle(cx))
472                .size_full()
473                .child(ProfileModalHeader::new("Agent Profiles", None))
474                .child(
475                    v_flex()
476                        .pb_1()
477                        .child(ListSeparator)
478                        .children(
479                            mode.builtin_profiles
480                                .iter()
481                                .map(|profile| self.render_profile(profile, window, cx)),
482                        )
483                        .when(!mode.custom_profiles.is_empty(), |this| {
484                            this.child(ListSeparator)
485                                .child(
486                                    div().pl_2().pb_1().child(
487                                        Label::new("Custom Profiles")
488                                            .size(LabelSize::Small)
489                                            .color(Color::Muted),
490                                    ),
491                                )
492                                .children(
493                                    mode.custom_profiles
494                                        .iter()
495                                        .map(|profile| self.render_profile(profile, window, cx)),
496                                )
497                        })
498                        .child(ListSeparator)
499                        .child(
500                            div()
501                                .id("new-profile")
502                                .track_focus(&mode.add_new_profile.focus_handle)
503                                .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
504                                    this.new_profile(None, window, cx);
505                                }))
506                                .child(
507                                    ListItem::new("new-profile")
508                                        .toggle_state(
509                                            mode.add_new_profile
510                                                .focus_handle
511                                                .contains_focused(window, cx),
512                                        )
513                                        .inset(true)
514                                        .spacing(ListItemSpacing::Sparse)
515                                        .start_slot(Icon::new(IconName::Plus))
516                                        .child(Label::new("Add New Profile"))
517                                        .on_click({
518                                            cx.listener(move |this, _, window, cx| {
519                                                this.new_profile(None, window, cx);
520                                            })
521                                        }),
522                                ),
523                        ),
524                )
525                .into_any_element(),
526        )
527        .map(|mut navigable| {
528            for profile in mode.builtin_profiles {
529                navigable = navigable.entry(profile.navigation);
530            }
531            for profile in mode.custom_profiles {
532                navigable = navigable.entry(profile.navigation);
533            }
534
535            navigable
536        })
537        .entry(mode.add_new_profile)
538    }
539
540    fn render_new_profile(
541        &mut self,
542        mode: NewProfileMode,
543        _window: &mut Window,
544        cx: &mut Context<Self>,
545    ) -> impl IntoElement {
546        let settings = AgentSettings::get_global(cx);
547
548        let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
549            settings
550                .profiles
551                .get(base_profile_id)
552                .map(|profile| profile.name.clone())
553                .unwrap_or_else(|| "Unknown".into())
554        });
555
556        v_flex()
557            .id("new-profile")
558            .track_focus(&self.focus_handle(cx))
559            .child(ProfileModalHeader::new(
560                match &base_profile_name {
561                    Some(base_profile) => format!("Fork {base_profile}"),
562                    None => "New Profile".into(),
563                },
564                match base_profile_name {
565                    Some(_) => Some(IconName::Scissors),
566                    None => Some(IconName::Plus),
567                },
568            ))
569            .child(ListSeparator)
570            .child(h_flex().p_2().child(mode.name_editor))
571    }
572
573    fn render_view_profile(
574        &mut self,
575        mode: ViewProfileMode,
576        window: &mut Window,
577        cx: &mut Context<Self>,
578    ) -> impl IntoElement {
579        let settings = AgentSettings::get_global(cx);
580
581        let profile_name = settings
582            .profiles
583            .get(&mode.profile_id)
584            .map(|profile| profile.name.clone())
585            .unwrap_or_else(|| "Unknown".into());
586
587        let icon = match mode.profile_id.as_str() {
588            "write" => IconName::Pencil,
589            "ask" => IconName::Chat,
590            _ => IconName::UserRoundPen,
591        };
592
593        Navigable::new(
594            div()
595                .track_focus(&self.focus_handle(cx))
596                .size_full()
597                .child(ProfileModalHeader::new(profile_name, Some(icon)))
598                .child(
599                    v_flex()
600                        .pb_1()
601                        .child(ListSeparator)
602                        .child(
603                            div()
604                                .id("fork-profile")
605                                .track_focus(&mode.fork_profile.focus_handle)
606                                .on_action({
607                                    let profile_id = mode.profile_id.clone();
608                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
609                                        this.new_profile(Some(profile_id.clone()), window, cx);
610                                    })
611                                })
612                                .child(
613                                    ListItem::new("fork-profile")
614                                        .toggle_state(
615                                            mode.fork_profile
616                                                .focus_handle
617                                                .contains_focused(window, cx),
618                                        )
619                                        .inset(true)
620                                        .spacing(ListItemSpacing::Sparse)
621                                        .start_slot(
622                                            Icon::new(IconName::Scissors)
623                                                .size(IconSize::Small)
624                                                .color(Color::Muted),
625                                        )
626                                        .child(Label::new("Fork Profile"))
627                                        .on_click({
628                                            let profile_id = mode.profile_id.clone();
629                                            cx.listener(move |this, _, window, cx| {
630                                                this.new_profile(
631                                                    Some(profile_id.clone()),
632                                                    window,
633                                                    cx,
634                                                );
635                                            })
636                                        }),
637                                ),
638                        )
639                        .child(
640                            div()
641                                .id("configure-default-model")
642                                .track_focus(&mode.configure_default_model.focus_handle)
643                                .on_action({
644                                    let profile_id = mode.profile_id.clone();
645                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
646                                        this.configure_default_model(
647                                            profile_id.clone(),
648                                            window,
649                                            cx,
650                                        );
651                                    })
652                                })
653                                .child(
654                                    ListItem::new("model-item")
655                                        .toggle_state(
656                                            mode.configure_default_model
657                                                .focus_handle
658                                                .contains_focused(window, cx),
659                                        )
660                                        .inset(true)
661                                        .spacing(ListItemSpacing::Sparse)
662                                        .start_slot(
663                                            Icon::new(IconName::ZedAssistant)
664                                                .size(IconSize::Small)
665                                                .color(Color::Muted),
666                                        )
667                                        .child(Label::new("Configure Default Model"))
668                                        .on_click({
669                                            let profile_id = mode.profile_id.clone();
670                                            cx.listener(move |this, _, window, cx| {
671                                                this.configure_default_model(
672                                                    profile_id.clone(),
673                                                    window,
674                                                    cx,
675                                                );
676                                            })
677                                        }),
678                                ),
679                        )
680                        .child(
681                            div()
682                                .id("configure-builtin-tools")
683                                .track_focus(&mode.configure_tools.focus_handle)
684                                .on_action({
685                                    let profile_id = mode.profile_id.clone();
686                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
687                                        this.configure_builtin_tools(
688                                            profile_id.clone(),
689                                            window,
690                                            cx,
691                                        );
692                                    })
693                                })
694                                .child(
695                                    ListItem::new("configure-builtin-tools-item")
696                                        .toggle_state(
697                                            mode.configure_tools
698                                                .focus_handle
699                                                .contains_focused(window, cx),
700                                        )
701                                        .inset(true)
702                                        .spacing(ListItemSpacing::Sparse)
703                                        .start_slot(
704                                            Icon::new(IconName::Settings)
705                                                .size(IconSize::Small)
706                                                .color(Color::Muted),
707                                        )
708                                        .child(Label::new("Configure Built-in Tools"))
709                                        .on_click({
710                                            let profile_id = mode.profile_id.clone();
711                                            cx.listener(move |this, _, window, cx| {
712                                                this.configure_builtin_tools(
713                                                    profile_id.clone(),
714                                                    window,
715                                                    cx,
716                                                );
717                                            })
718                                        }),
719                                ),
720                        )
721                        .child(
722                            div()
723                                .id("configure-mcps")
724                                .track_focus(&mode.configure_mcps.focus_handle)
725                                .on_action({
726                                    let profile_id = mode.profile_id.clone();
727                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
728                                        this.configure_mcp_tools(profile_id.clone(), window, cx);
729                                    })
730                                })
731                                .child(
732                                    ListItem::new("configure-mcp-tools")
733                                        .toggle_state(
734                                            mode.configure_mcps
735                                                .focus_handle
736                                                .contains_focused(window, cx),
737                                        )
738                                        .inset(true)
739                                        .spacing(ListItemSpacing::Sparse)
740                                        .start_slot(
741                                            Icon::new(IconName::ToolHammer)
742                                                .size(IconSize::Small)
743                                                .color(Color::Muted),
744                                        )
745                                        .child(Label::new("Configure MCP Tools"))
746                                        .on_click({
747                                            let profile_id = mode.profile_id.clone();
748                                            cx.listener(move |this, _, window, cx| {
749                                                this.configure_mcp_tools(
750                                                    profile_id.clone(),
751                                                    window,
752                                                    cx,
753                                                );
754                                            })
755                                        }),
756                                ),
757                        )
758                        .child(ListSeparator)
759                        .child(
760                            div()
761                                .id("cancel-item")
762                                .track_focus(&mode.cancel_item.focus_handle)
763                                .on_action({
764                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
765                                        this.cancel(window, cx);
766                                    })
767                                })
768                                .child(
769                                    ListItem::new("cancel-item")
770                                        .toggle_state(
771                                            mode.cancel_item
772                                                .focus_handle
773                                                .contains_focused(window, cx),
774                                        )
775                                        .inset(true)
776                                        .spacing(ListItemSpacing::Sparse)
777                                        .start_slot(
778                                            Icon::new(IconName::ArrowLeft)
779                                                .size(IconSize::Small)
780                                                .color(Color::Muted),
781                                        )
782                                        .child(Label::new("Go Back"))
783                                        .end_slot(
784                                            div().child(
785                                                KeyBinding::for_action_in(
786                                                    &menu::Cancel,
787                                                    &self.focus_handle,
788                                                    cx,
789                                                )
790                                                .size(rems_from_px(12.)),
791                                            ),
792                                        )
793                                        .on_click({
794                                            cx.listener(move |this, _, window, cx| {
795                                                this.cancel(window, cx);
796                                            })
797                                        }),
798                                ),
799                        ),
800                )
801                .into_any_element(),
802        )
803        .entry(mode.fork_profile)
804        .entry(mode.configure_default_model)
805        .entry(mode.configure_tools)
806        .entry(mode.configure_mcps)
807        .entry(mode.cancel_item)
808    }
809}
810
811impl Render for ManageProfilesModal {
812    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
813        let settings = AgentSettings::get_global(cx);
814
815        let go_back_item = div()
816            .id("cancel-item")
817            .track_focus(&self.focus_handle)
818            .on_action({
819                cx.listener(move |this, _: &menu::Confirm, window, cx| {
820                    this.cancel(window, cx);
821                })
822            })
823            .child(
824                ListItem::new("cancel-item")
825                    .toggle_state(self.focus_handle.contains_focused(window, cx))
826                    .inset(true)
827                    .spacing(ListItemSpacing::Sparse)
828                    .start_slot(
829                        Icon::new(IconName::ArrowLeft)
830                            .size(IconSize::Small)
831                            .color(Color::Muted),
832                    )
833                    .child(Label::new("Go Back"))
834                    .end_slot(
835                        div().child(
836                            KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
837                                .size(rems_from_px(12.)),
838                        ),
839                    )
840                    .on_click({
841                        cx.listener(move |this, _, window, cx| {
842                            this.cancel(window, cx);
843                        })
844                    }),
845            );
846
847        div()
848            .elevation_3(cx)
849            .w(rems(34.))
850            .key_context("ManageProfilesModal")
851            .on_action(cx.listener(|this, _: &menu::Cancel, window, cx| this.cancel(window, cx)))
852            .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| this.confirm(window, cx)))
853            .capture_any_mouse_down(cx.listener(|this, _, window, cx| {
854                this.focus_handle(cx).focus(window);
855            }))
856            .on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
857            .child(match &self.mode {
858                Mode::ChooseProfile(mode) => self
859                    .render_choose_profile(mode.clone(), window, cx)
860                    .into_any_element(),
861                Mode::NewProfile(mode) => self
862                    .render_new_profile(mode.clone(), window, cx)
863                    .into_any_element(),
864                Mode::ViewProfile(mode) => self
865                    .render_view_profile(mode.clone(), window, cx)
866                    .into_any_element(),
867                Mode::ConfigureTools {
868                    profile_id,
869                    tool_picker,
870                    ..
871                } => {
872                    let profile_name = settings
873                        .profiles
874                        .get(profile_id)
875                        .map(|profile| profile.name.clone())
876                        .unwrap_or_else(|| "Unknown".into());
877
878                    v_flex()
879                        .pb_1()
880                        .child(ProfileModalHeader::new(
881                            format!("{profile_name} — Configure Built-in Tools"),
882                            Some(IconName::Cog),
883                        ))
884                        .child(ListSeparator)
885                        .child(tool_picker.clone())
886                        .child(ListSeparator)
887                        .child(go_back_item)
888                        .into_any_element()
889                }
890                Mode::ConfigureDefaultModel {
891                    profile_id,
892                    model_picker,
893                    ..
894                } => {
895                    let profile_name = settings
896                        .profiles
897                        .get(profile_id)
898                        .map(|profile| profile.name.clone())
899                        .unwrap_or_else(|| "Unknown".into());
900
901                    v_flex()
902                        .pb_1()
903                        .child(ProfileModalHeader::new(
904                            format!("{profile_name} — Configure Default Model"),
905                            Some(IconName::Ai),
906                        ))
907                        .child(ListSeparator)
908                        .child(v_flex().w(rems(34.)).child(model_picker.clone()))
909                        .child(ListSeparator)
910                        .child(go_back_item)
911                        .into_any_element()
912                }
913                Mode::ConfigureMcps {
914                    profile_id,
915                    tool_picker,
916                    ..
917                } => {
918                    let profile_name = settings
919                        .profiles
920                        .get(profile_id)
921                        .map(|profile| profile.name.clone())
922                        .unwrap_or_else(|| "Unknown".into());
923
924                    v_flex()
925                        .pb_1()
926                        .child(ProfileModalHeader::new(
927                            format!("{profile_name} — Configure MCP Tools"),
928                            Some(IconName::ToolHammer),
929                        ))
930                        .child(ListSeparator)
931                        .child(tool_picker.clone())
932                        .child(ListSeparator)
933                        .child(go_back_item)
934                        .into_any_element()
935                }
936            })
937    }
938}