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                self.focus_handle.clone(),
257                window,
258                cx,
259            )
260            .modal(false)
261        });
262
263        let dismiss_subscription = cx.subscribe_in(&model_picker, window, {
264            let profile_id = profile_id.clone();
265            move |this, _picker, _: &DismissEvent, window, cx| {
266                this.view_profile(profile_id.clone(), window, cx);
267            }
268        });
269
270        self.mode = Mode::ConfigureDefaultModel {
271            profile_id,
272            model_picker,
273            _subscription: dismiss_subscription,
274        };
275        self.focus_handle(cx).focus(window);
276    }
277
278    fn configure_mcp_tools(
279        &mut self,
280        profile_id: AgentProfileId,
281        window: &mut Window,
282        cx: &mut Context<Self>,
283    ) {
284        let settings = AgentSettings::get_global(cx);
285        let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
286            return;
287        };
288
289        let tool_picker = cx.new(|cx| {
290            let delegate = ToolPickerDelegate::mcp_tools(
291                &self.context_server_registry,
292                self.fs.clone(),
293                profile_id.clone(),
294                profile,
295                cx,
296            );
297            ToolPicker::mcp_tools(delegate, window, cx)
298        });
299        let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
300            let profile_id = profile_id.clone();
301            move |this, _tool_picker, _: &DismissEvent, window, cx| {
302                this.view_profile(profile_id.clone(), window, cx);
303            }
304        });
305
306        self.mode = Mode::ConfigureMcps {
307            profile_id,
308            tool_picker,
309            _subscription: dismiss_subscription,
310        };
311        self.focus_handle(cx).focus(window);
312    }
313
314    fn configure_builtin_tools(
315        &mut self,
316        profile_id: AgentProfileId,
317        window: &mut Window,
318        cx: &mut Context<Self>,
319    ) {
320        let settings = AgentSettings::get_global(cx);
321        let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
322            return;
323        };
324
325        let tool_picker = cx.new(|cx| {
326            let delegate = ToolPickerDelegate::builtin_tools(
327                //todo: This causes the web search tool to show up even it only works when using zed hosted models
328                agent::supported_built_in_tool_names(
329                    self.active_model.as_ref().map(|model| model.provider_id()),
330                )
331                .map(|s| s.into())
332                .collect::<Vec<_>>(),
333                self.fs.clone(),
334                profile_id.clone(),
335                profile,
336                cx,
337            );
338            ToolPicker::builtin_tools(delegate, window, cx)
339        });
340        let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
341            let profile_id = profile_id.clone();
342            move |this, _tool_picker, _: &DismissEvent, window, cx| {
343                this.view_profile(profile_id.clone(), window, cx);
344            }
345        });
346
347        self.mode = Mode::ConfigureTools {
348            profile_id,
349            tool_picker,
350            _subscription: dismiss_subscription,
351        };
352        self.focus_handle(cx).focus(window);
353    }
354
355    fn confirm(&mut self, window: &mut Window, cx: &mut Context<Self>) {
356        match &self.mode {
357            Mode::ChooseProfile { .. } => {}
358            Mode::NewProfile(mode) => {
359                let name = mode.name_editor.read(cx).text(cx);
360
361                let profile_id =
362                    AgentProfile::create(name, mode.base_profile_id.clone(), self.fs.clone(), cx);
363                self.view_profile(profile_id, window, cx);
364            }
365            Mode::ViewProfile(_) => {}
366            Mode::ConfigureTools { .. } => {}
367            Mode::ConfigureMcps { .. } => {}
368            Mode::ConfigureDefaultModel { .. } => {}
369        }
370    }
371
372    fn cancel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
373        match &self.mode {
374            Mode::ChooseProfile { .. } => {
375                cx.emit(DismissEvent);
376            }
377            Mode::NewProfile(mode) => {
378                if let Some(profile_id) = mode.base_profile_id.clone() {
379                    self.view_profile(profile_id, window, cx);
380                } else {
381                    self.choose_profile(window, cx);
382                }
383            }
384            Mode::ViewProfile(_) => self.choose_profile(window, cx),
385            Mode::ConfigureTools { profile_id, .. } => {
386                self.view_profile(profile_id.clone(), window, cx)
387            }
388            Mode::ConfigureMcps { profile_id, .. } => {
389                self.view_profile(profile_id.clone(), window, cx)
390            }
391            Mode::ConfigureDefaultModel { profile_id, .. } => {
392                self.view_profile(profile_id.clone(), window, cx)
393            }
394        }
395    }
396}
397
398impl ModalView for ManageProfilesModal {}
399
400impl Focusable for ManageProfilesModal {
401    fn focus_handle(&self, cx: &App) -> FocusHandle {
402        match &self.mode {
403            Mode::ChooseProfile(_) => self.focus_handle.clone(),
404            Mode::NewProfile(mode) => mode.name_editor.focus_handle(cx),
405            Mode::ViewProfile(_) => self.focus_handle.clone(),
406            Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx),
407            Mode::ConfigureMcps { tool_picker, .. } => tool_picker.focus_handle(cx),
408            Mode::ConfigureDefaultModel { model_picker, .. } => model_picker.focus_handle(cx),
409        }
410    }
411}
412
413impl EventEmitter<DismissEvent> for ManageProfilesModal {}
414
415impl ManageProfilesModal {
416    fn render_profile(
417        &self,
418        profile: &ProfileEntry,
419        window: &mut Window,
420        cx: &mut Context<Self>,
421    ) -> impl IntoElement + use<> {
422        let is_focused = profile.navigation.focus_handle.contains_focused(window, cx);
423
424        div()
425            .id(SharedString::from(format!("profile-{}", profile.id)))
426            .track_focus(&profile.navigation.focus_handle)
427            .on_action({
428                let profile_id = profile.id.clone();
429                cx.listener(move |this, _: &menu::Confirm, window, cx| {
430                    this.view_profile(profile_id.clone(), window, cx);
431                })
432            })
433            .child(
434                ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
435                    .toggle_state(is_focused)
436                    .inset(true)
437                    .spacing(ListItemSpacing::Sparse)
438                    .child(Label::new(profile.name.clone()))
439                    .when(is_focused, |this| {
440                        this.end_slot(
441                            h_flex()
442                                .gap_1()
443                                .child(
444                                    Label::new("Customize")
445                                        .size(LabelSize::Small)
446                                        .color(Color::Muted),
447                                )
448                                .child(KeyBinding::for_action_in(
449                                    &menu::Confirm,
450                                    &self.focus_handle,
451                                    cx,
452                                )),
453                        )
454                    })
455                    .on_click({
456                        let profile_id = profile.id.clone();
457                        cx.listener(move |this, _, window, cx| {
458                            this.view_profile(profile_id.clone(), window, cx);
459                        })
460                    }),
461            )
462    }
463
464    fn render_choose_profile(
465        &mut self,
466        mode: ChooseProfileMode,
467        window: &mut Window,
468        cx: &mut Context<Self>,
469    ) -> impl IntoElement {
470        Navigable::new(
471            div()
472                .track_focus(&self.focus_handle(cx))
473                .size_full()
474                .child(ProfileModalHeader::new("Agent Profiles", None))
475                .child(
476                    v_flex()
477                        .pb_1()
478                        .child(ListSeparator)
479                        .children(
480                            mode.builtin_profiles
481                                .iter()
482                                .map(|profile| self.render_profile(profile, window, cx)),
483                        )
484                        .when(!mode.custom_profiles.is_empty(), |this| {
485                            this.child(ListSeparator)
486                                .child(
487                                    div().pl_2().pb_1().child(
488                                        Label::new("Custom Profiles")
489                                            .size(LabelSize::Small)
490                                            .color(Color::Muted),
491                                    ),
492                                )
493                                .children(
494                                    mode.custom_profiles
495                                        .iter()
496                                        .map(|profile| self.render_profile(profile, window, cx)),
497                                )
498                        })
499                        .child(ListSeparator)
500                        .child(
501                            div()
502                                .id("new-profile")
503                                .track_focus(&mode.add_new_profile.focus_handle)
504                                .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
505                                    this.new_profile(None, window, cx);
506                                }))
507                                .child(
508                                    ListItem::new("new-profile")
509                                        .toggle_state(
510                                            mode.add_new_profile
511                                                .focus_handle
512                                                .contains_focused(window, cx),
513                                        )
514                                        .inset(true)
515                                        .spacing(ListItemSpacing::Sparse)
516                                        .start_slot(Icon::new(IconName::Plus))
517                                        .child(Label::new("Add New Profile"))
518                                        .on_click({
519                                            cx.listener(move |this, _, window, cx| {
520                                                this.new_profile(None, window, cx);
521                                            })
522                                        }),
523                                ),
524                        ),
525                )
526                .into_any_element(),
527        )
528        .map(|mut navigable| {
529            for profile in mode.builtin_profiles {
530                navigable = navigable.entry(profile.navigation);
531            }
532            for profile in mode.custom_profiles {
533                navigable = navigable.entry(profile.navigation);
534            }
535
536            navigable
537        })
538        .entry(mode.add_new_profile)
539    }
540
541    fn render_new_profile(
542        &mut self,
543        mode: NewProfileMode,
544        _window: &mut Window,
545        cx: &mut Context<Self>,
546    ) -> impl IntoElement {
547        let settings = AgentSettings::get_global(cx);
548
549        let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
550            settings
551                .profiles
552                .get(base_profile_id)
553                .map(|profile| profile.name.clone())
554                .unwrap_or_else(|| "Unknown".into())
555        });
556
557        v_flex()
558            .id("new-profile")
559            .track_focus(&self.focus_handle(cx))
560            .child(ProfileModalHeader::new(
561                match &base_profile_name {
562                    Some(base_profile) => format!("Fork {base_profile}"),
563                    None => "New Profile".into(),
564                },
565                match base_profile_name {
566                    Some(_) => Some(IconName::Scissors),
567                    None => Some(IconName::Plus),
568                },
569            ))
570            .child(ListSeparator)
571            .child(h_flex().p_2().child(mode.name_editor))
572    }
573
574    fn render_view_profile(
575        &mut self,
576        mode: ViewProfileMode,
577        window: &mut Window,
578        cx: &mut Context<Self>,
579    ) -> impl IntoElement {
580        let settings = AgentSettings::get_global(cx);
581
582        let profile_name = settings
583            .profiles
584            .get(&mode.profile_id)
585            .map(|profile| profile.name.clone())
586            .unwrap_or_else(|| "Unknown".into());
587
588        let icon = match mode.profile_id.as_str() {
589            "write" => IconName::Pencil,
590            "ask" => IconName::Chat,
591            _ => IconName::UserRoundPen,
592        };
593
594        Navigable::new(
595            div()
596                .track_focus(&self.focus_handle(cx))
597                .size_full()
598                .child(ProfileModalHeader::new(profile_name, Some(icon)))
599                .child(
600                    v_flex()
601                        .pb_1()
602                        .child(ListSeparator)
603                        .child(
604                            div()
605                                .id("fork-profile")
606                                .track_focus(&mode.fork_profile.focus_handle)
607                                .on_action({
608                                    let profile_id = mode.profile_id.clone();
609                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
610                                        this.new_profile(Some(profile_id.clone()), window, cx);
611                                    })
612                                })
613                                .child(
614                                    ListItem::new("fork-profile")
615                                        .toggle_state(
616                                            mode.fork_profile
617                                                .focus_handle
618                                                .contains_focused(window, cx),
619                                        )
620                                        .inset(true)
621                                        .spacing(ListItemSpacing::Sparse)
622                                        .start_slot(
623                                            Icon::new(IconName::Scissors)
624                                                .size(IconSize::Small)
625                                                .color(Color::Muted),
626                                        )
627                                        .child(Label::new("Fork Profile"))
628                                        .on_click({
629                                            let profile_id = mode.profile_id.clone();
630                                            cx.listener(move |this, _, window, cx| {
631                                                this.new_profile(
632                                                    Some(profile_id.clone()),
633                                                    window,
634                                                    cx,
635                                                );
636                                            })
637                                        }),
638                                ),
639                        )
640                        .child(
641                            div()
642                                .id("configure-default-model")
643                                .track_focus(&mode.configure_default_model.focus_handle)
644                                .on_action({
645                                    let profile_id = mode.profile_id.clone();
646                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
647                                        this.configure_default_model(
648                                            profile_id.clone(),
649                                            window,
650                                            cx,
651                                        );
652                                    })
653                                })
654                                .child(
655                                    ListItem::new("model-item")
656                                        .toggle_state(
657                                            mode.configure_default_model
658                                                .focus_handle
659                                                .contains_focused(window, cx),
660                                        )
661                                        .inset(true)
662                                        .spacing(ListItemSpacing::Sparse)
663                                        .start_slot(
664                                            Icon::new(IconName::ZedAssistant)
665                                                .size(IconSize::Small)
666                                                .color(Color::Muted),
667                                        )
668                                        .child(Label::new("Configure Default Model"))
669                                        .on_click({
670                                            let profile_id = mode.profile_id.clone();
671                                            cx.listener(move |this, _, window, cx| {
672                                                this.configure_default_model(
673                                                    profile_id.clone(),
674                                                    window,
675                                                    cx,
676                                                );
677                                            })
678                                        }),
679                                ),
680                        )
681                        .child(
682                            div()
683                                .id("configure-builtin-tools")
684                                .track_focus(&mode.configure_tools.focus_handle)
685                                .on_action({
686                                    let profile_id = mode.profile_id.clone();
687                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
688                                        this.configure_builtin_tools(
689                                            profile_id.clone(),
690                                            window,
691                                            cx,
692                                        );
693                                    })
694                                })
695                                .child(
696                                    ListItem::new("configure-builtin-tools-item")
697                                        .toggle_state(
698                                            mode.configure_tools
699                                                .focus_handle
700                                                .contains_focused(window, cx),
701                                        )
702                                        .inset(true)
703                                        .spacing(ListItemSpacing::Sparse)
704                                        .start_slot(
705                                            Icon::new(IconName::Settings)
706                                                .size(IconSize::Small)
707                                                .color(Color::Muted),
708                                        )
709                                        .child(Label::new("Configure Built-in Tools"))
710                                        .on_click({
711                                            let profile_id = mode.profile_id.clone();
712                                            cx.listener(move |this, _, window, cx| {
713                                                this.configure_builtin_tools(
714                                                    profile_id.clone(),
715                                                    window,
716                                                    cx,
717                                                );
718                                            })
719                                        }),
720                                ),
721                        )
722                        .child(
723                            div()
724                                .id("configure-mcps")
725                                .track_focus(&mode.configure_mcps.focus_handle)
726                                .on_action({
727                                    let profile_id = mode.profile_id.clone();
728                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
729                                        this.configure_mcp_tools(profile_id.clone(), window, cx);
730                                    })
731                                })
732                                .child(
733                                    ListItem::new("configure-mcp-tools")
734                                        .toggle_state(
735                                            mode.configure_mcps
736                                                .focus_handle
737                                                .contains_focused(window, cx),
738                                        )
739                                        .inset(true)
740                                        .spacing(ListItemSpacing::Sparse)
741                                        .start_slot(
742                                            Icon::new(IconName::ToolHammer)
743                                                .size(IconSize::Small)
744                                                .color(Color::Muted),
745                                        )
746                                        .child(Label::new("Configure MCP Tools"))
747                                        .on_click({
748                                            let profile_id = mode.profile_id.clone();
749                                            cx.listener(move |this, _, window, cx| {
750                                                this.configure_mcp_tools(
751                                                    profile_id.clone(),
752                                                    window,
753                                                    cx,
754                                                );
755                                            })
756                                        }),
757                                ),
758                        )
759                        .child(ListSeparator)
760                        .child(
761                            div()
762                                .id("cancel-item")
763                                .track_focus(&mode.cancel_item.focus_handle)
764                                .on_action({
765                                    cx.listener(move |this, _: &menu::Confirm, window, cx| {
766                                        this.cancel(window, cx);
767                                    })
768                                })
769                                .child(
770                                    ListItem::new("cancel-item")
771                                        .toggle_state(
772                                            mode.cancel_item
773                                                .focus_handle
774                                                .contains_focused(window, cx),
775                                        )
776                                        .inset(true)
777                                        .spacing(ListItemSpacing::Sparse)
778                                        .start_slot(
779                                            Icon::new(IconName::ArrowLeft)
780                                                .size(IconSize::Small)
781                                                .color(Color::Muted),
782                                        )
783                                        .child(Label::new("Go Back"))
784                                        .end_slot(
785                                            div().child(
786                                                KeyBinding::for_action_in(
787                                                    &menu::Cancel,
788                                                    &self.focus_handle,
789                                                    cx,
790                                                )
791                                                .size(rems_from_px(12.)),
792                                            ),
793                                        )
794                                        .on_click({
795                                            cx.listener(move |this, _, window, cx| {
796                                                this.cancel(window, cx);
797                                            })
798                                        }),
799                                ),
800                        ),
801                )
802                .into_any_element(),
803        )
804        .entry(mode.fork_profile)
805        .entry(mode.configure_default_model)
806        .entry(mode.configure_tools)
807        .entry(mode.configure_mcps)
808        .entry(mode.cancel_item)
809    }
810}
811
812impl Render for ManageProfilesModal {
813    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
814        let settings = AgentSettings::get_global(cx);
815
816        let go_back_item = div()
817            .id("cancel-item")
818            .track_focus(&self.focus_handle)
819            .on_action({
820                cx.listener(move |this, _: &menu::Confirm, window, cx| {
821                    this.cancel(window, cx);
822                })
823            })
824            .child(
825                ListItem::new("cancel-item")
826                    .toggle_state(self.focus_handle.contains_focused(window, cx))
827                    .inset(true)
828                    .spacing(ListItemSpacing::Sparse)
829                    .start_slot(
830                        Icon::new(IconName::ArrowLeft)
831                            .size(IconSize::Small)
832                            .color(Color::Muted),
833                    )
834                    .child(Label::new("Go Back"))
835                    .end_slot(
836                        div().child(
837                            KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
838                                .size(rems_from_px(12.)),
839                        ),
840                    )
841                    .on_click({
842                        cx.listener(move |this, _, window, cx| {
843                            this.cancel(window, cx);
844                        })
845                    }),
846            );
847
848        div()
849            .elevation_3(cx)
850            .w(rems(34.))
851            .key_context("ManageProfilesModal")
852            .on_action(cx.listener(|this, _: &menu::Cancel, window, cx| this.cancel(window, cx)))
853            .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| this.confirm(window, cx)))
854            .capture_any_mouse_down(cx.listener(|this, _, window, cx| {
855                this.focus_handle(cx).focus(window);
856            }))
857            .on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
858            .child(match &self.mode {
859                Mode::ChooseProfile(mode) => self
860                    .render_choose_profile(mode.clone(), window, cx)
861                    .into_any_element(),
862                Mode::NewProfile(mode) => self
863                    .render_new_profile(mode.clone(), window, cx)
864                    .into_any_element(),
865                Mode::ViewProfile(mode) => self
866                    .render_view_profile(mode.clone(), window, cx)
867                    .into_any_element(),
868                Mode::ConfigureTools {
869                    profile_id,
870                    tool_picker,
871                    ..
872                } => {
873                    let profile_name = settings
874                        .profiles
875                        .get(profile_id)
876                        .map(|profile| profile.name.clone())
877                        .unwrap_or_else(|| "Unknown".into());
878
879                    v_flex()
880                        .pb_1()
881                        .child(ProfileModalHeader::new(
882                            format!("{profile_name} — Configure Built-in Tools"),
883                            Some(IconName::Cog),
884                        ))
885                        .child(ListSeparator)
886                        .child(tool_picker.clone())
887                        .child(ListSeparator)
888                        .child(go_back_item)
889                        .into_any_element()
890                }
891                Mode::ConfigureDefaultModel {
892                    profile_id,
893                    model_picker,
894                    ..
895                } => {
896                    let profile_name = settings
897                        .profiles
898                        .get(profile_id)
899                        .map(|profile| profile.name.clone())
900                        .unwrap_or_else(|| "Unknown".into());
901
902                    v_flex()
903                        .pb_1()
904                        .child(ProfileModalHeader::new(
905                            format!("{profile_name} — Configure Default Model"),
906                            Some(IconName::Ai),
907                        ))
908                        .child(ListSeparator)
909                        .child(v_flex().w(rems(34.)).child(model_picker.clone()))
910                        .child(ListSeparator)
911                        .child(go_back_item)
912                        .into_any_element()
913                }
914                Mode::ConfigureMcps {
915                    profile_id,
916                    tool_picker,
917                    ..
918                } => {
919                    let profile_name = settings
920                        .profiles
921                        .get(profile_id)
922                        .map(|profile| profile.name.clone())
923                        .unwrap_or_else(|| "Unknown".into());
924
925                    v_flex()
926                        .pb_1()
927                        .child(ProfileModalHeader::new(
928                            format!("{profile_name} — Configure MCP Tools"),
929                            Some(IconName::ToolHammer),
930                        ))
931                        .child(ListSeparator)
932                        .child(tool_picker.clone())
933                        .child(ListSeparator)
934                        .child(go_back_item)
935                        .into_any_element()
936                }
937            })
938    }
939}