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