manage_profiles_modal.rs

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