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