manage_profiles_modal.rs

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