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