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