manage_profiles_modal.rs

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