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