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}