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 window,
257 cx,
258 )
259 .modal(false)
260 });
261
262 let dismiss_subscription = cx.subscribe_in(&model_picker, window, {
263 let profile_id = profile_id.clone();
264 move |this, _picker, _: &DismissEvent, window, cx| {
265 this.view_profile(profile_id.clone(), window, cx);
266 }
267 });
268
269 self.mode = Mode::ConfigureDefaultModel {
270 profile_id,
271 model_picker,
272 _subscription: dismiss_subscription,
273 };
274 self.focus_handle(cx).focus(window);
275 }
276
277 fn configure_mcp_tools(
278 &mut self,
279 profile_id: AgentProfileId,
280 window: &mut Window,
281 cx: &mut Context<Self>,
282 ) {
283 let settings = AgentSettings::get_global(cx);
284 let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
285 return;
286 };
287
288 let tool_picker = cx.new(|cx| {
289 let delegate = ToolPickerDelegate::mcp_tools(
290 &self.context_server_registry,
291 self.fs.clone(),
292 profile_id.clone(),
293 profile,
294 cx,
295 );
296 ToolPicker::mcp_tools(delegate, window, cx)
297 });
298 let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
299 let profile_id = profile_id.clone();
300 move |this, _tool_picker, _: &DismissEvent, window, cx| {
301 this.view_profile(profile_id.clone(), window, cx);
302 }
303 });
304
305 self.mode = Mode::ConfigureMcps {
306 profile_id,
307 tool_picker,
308 _subscription: dismiss_subscription,
309 };
310 self.focus_handle(cx).focus(window);
311 }
312
313 fn configure_builtin_tools(
314 &mut self,
315 profile_id: AgentProfileId,
316 window: &mut Window,
317 cx: &mut Context<Self>,
318 ) {
319 let settings = AgentSettings::get_global(cx);
320 let Some(profile) = settings.profiles.get(&profile_id).cloned() else {
321 return;
322 };
323
324 let tool_picker = cx.new(|cx| {
325 let delegate = ToolPickerDelegate::builtin_tools(
326 //todo: This causes the web search tool to show up even it only works when using zed hosted models
327 agent::supported_built_in_tool_names(
328 self.active_model.as_ref().map(|model| model.provider_id()),
329 )
330 .map(|s| s.into())
331 .collect::<Vec<_>>(),
332 self.fs.clone(),
333 profile_id.clone(),
334 profile,
335 cx,
336 );
337 ToolPicker::builtin_tools(delegate, window, cx)
338 });
339 let dismiss_subscription = cx.subscribe_in(&tool_picker, window, {
340 let profile_id = profile_id.clone();
341 move |this, _tool_picker, _: &DismissEvent, window, cx| {
342 this.view_profile(profile_id.clone(), window, cx);
343 }
344 });
345
346 self.mode = Mode::ConfigureTools {
347 profile_id,
348 tool_picker,
349 _subscription: dismiss_subscription,
350 };
351 self.focus_handle(cx).focus(window);
352 }
353
354 fn confirm(&mut self, window: &mut Window, cx: &mut Context<Self>) {
355 match &self.mode {
356 Mode::ChooseProfile { .. } => {}
357 Mode::NewProfile(mode) => {
358 let name = mode.name_editor.read(cx).text(cx);
359
360 let profile_id =
361 AgentProfile::create(name, mode.base_profile_id.clone(), self.fs.clone(), cx);
362 self.view_profile(profile_id, window, cx);
363 }
364 Mode::ViewProfile(_) => {}
365 Mode::ConfigureTools { .. } => {}
366 Mode::ConfigureMcps { .. } => {}
367 Mode::ConfigureDefaultModel { .. } => {}
368 }
369 }
370
371 fn cancel(&mut self, window: &mut Window, cx: &mut Context<Self>) {
372 match &self.mode {
373 Mode::ChooseProfile { .. } => {
374 cx.emit(DismissEvent);
375 }
376 Mode::NewProfile(mode) => {
377 if let Some(profile_id) = mode.base_profile_id.clone() {
378 self.view_profile(profile_id, window, cx);
379 } else {
380 self.choose_profile(window, cx);
381 }
382 }
383 Mode::ViewProfile(_) => self.choose_profile(window, cx),
384 Mode::ConfigureTools { profile_id, .. } => {
385 self.view_profile(profile_id.clone(), window, cx)
386 }
387 Mode::ConfigureMcps { profile_id, .. } => {
388 self.view_profile(profile_id.clone(), window, cx)
389 }
390 Mode::ConfigureDefaultModel { profile_id, .. } => {
391 self.view_profile(profile_id.clone(), window, cx)
392 }
393 }
394 }
395}
396
397impl ModalView for ManageProfilesModal {}
398
399impl Focusable for ManageProfilesModal {
400 fn focus_handle(&self, cx: &App) -> FocusHandle {
401 match &self.mode {
402 Mode::ChooseProfile(_) => self.focus_handle.clone(),
403 Mode::NewProfile(mode) => mode.name_editor.focus_handle(cx),
404 Mode::ViewProfile(_) => self.focus_handle.clone(),
405 Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx),
406 Mode::ConfigureMcps { tool_picker, .. } => tool_picker.focus_handle(cx),
407 Mode::ConfigureDefaultModel { model_picker, .. } => model_picker.focus_handle(cx),
408 }
409 }
410}
411
412impl EventEmitter<DismissEvent> for ManageProfilesModal {}
413
414impl ManageProfilesModal {
415 fn render_profile(
416 &self,
417 profile: &ProfileEntry,
418 window: &mut Window,
419 cx: &mut Context<Self>,
420 ) -> impl IntoElement + use<> {
421 let is_focused = profile.navigation.focus_handle.contains_focused(window, cx);
422
423 div()
424 .id(SharedString::from(format!("profile-{}", profile.id)))
425 .track_focus(&profile.navigation.focus_handle)
426 .on_action({
427 let profile_id = profile.id.clone();
428 cx.listener(move |this, _: &menu::Confirm, window, cx| {
429 this.view_profile(profile_id.clone(), window, cx);
430 })
431 })
432 .child(
433 ListItem::new(SharedString::from(format!("profile-{}", profile.id)))
434 .toggle_state(is_focused)
435 .inset(true)
436 .spacing(ListItemSpacing::Sparse)
437 .child(Label::new(profile.name.clone()))
438 .when(is_focused, |this| {
439 this.end_slot(
440 h_flex()
441 .gap_1()
442 .child(
443 Label::new("Customize")
444 .size(LabelSize::Small)
445 .color(Color::Muted),
446 )
447 .child(KeyBinding::for_action_in(
448 &menu::Confirm,
449 &self.focus_handle,
450 cx,
451 )),
452 )
453 })
454 .on_click({
455 let profile_id = profile.id.clone();
456 cx.listener(move |this, _, window, cx| {
457 this.view_profile(profile_id.clone(), window, cx);
458 })
459 }),
460 )
461 }
462
463 fn render_choose_profile(
464 &mut self,
465 mode: ChooseProfileMode,
466 window: &mut Window,
467 cx: &mut Context<Self>,
468 ) -> impl IntoElement {
469 Navigable::new(
470 div()
471 .track_focus(&self.focus_handle(cx))
472 .size_full()
473 .child(ProfileModalHeader::new("Agent Profiles", None))
474 .child(
475 v_flex()
476 .pb_1()
477 .child(ListSeparator)
478 .children(
479 mode.builtin_profiles
480 .iter()
481 .map(|profile| self.render_profile(profile, window, cx)),
482 )
483 .when(!mode.custom_profiles.is_empty(), |this| {
484 this.child(ListSeparator)
485 .child(
486 div().pl_2().pb_1().child(
487 Label::new("Custom Profiles")
488 .size(LabelSize::Small)
489 .color(Color::Muted),
490 ),
491 )
492 .children(
493 mode.custom_profiles
494 .iter()
495 .map(|profile| self.render_profile(profile, window, cx)),
496 )
497 })
498 .child(ListSeparator)
499 .child(
500 div()
501 .id("new-profile")
502 .track_focus(&mode.add_new_profile.focus_handle)
503 .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
504 this.new_profile(None, window, cx);
505 }))
506 .child(
507 ListItem::new("new-profile")
508 .toggle_state(
509 mode.add_new_profile
510 .focus_handle
511 .contains_focused(window, cx),
512 )
513 .inset(true)
514 .spacing(ListItemSpacing::Sparse)
515 .start_slot(Icon::new(IconName::Plus))
516 .child(Label::new("Add New Profile"))
517 .on_click({
518 cx.listener(move |this, _, window, cx| {
519 this.new_profile(None, window, cx);
520 })
521 }),
522 ),
523 ),
524 )
525 .into_any_element(),
526 )
527 .map(|mut navigable| {
528 for profile in mode.builtin_profiles {
529 navigable = navigable.entry(profile.navigation);
530 }
531 for profile in mode.custom_profiles {
532 navigable = navigable.entry(profile.navigation);
533 }
534
535 navigable
536 })
537 .entry(mode.add_new_profile)
538 }
539
540 fn render_new_profile(
541 &mut self,
542 mode: NewProfileMode,
543 _window: &mut Window,
544 cx: &mut Context<Self>,
545 ) -> impl IntoElement {
546 let settings = AgentSettings::get_global(cx);
547
548 let base_profile_name = mode.base_profile_id.as_ref().map(|base_profile_id| {
549 settings
550 .profiles
551 .get(base_profile_id)
552 .map(|profile| profile.name.clone())
553 .unwrap_or_else(|| "Unknown".into())
554 });
555
556 v_flex()
557 .id("new-profile")
558 .track_focus(&self.focus_handle(cx))
559 .child(ProfileModalHeader::new(
560 match &base_profile_name {
561 Some(base_profile) => format!("Fork {base_profile}"),
562 None => "New Profile".into(),
563 },
564 match base_profile_name {
565 Some(_) => Some(IconName::Scissors),
566 None => Some(IconName::Plus),
567 },
568 ))
569 .child(ListSeparator)
570 .child(h_flex().p_2().child(mode.name_editor))
571 }
572
573 fn render_view_profile(
574 &mut self,
575 mode: ViewProfileMode,
576 window: &mut Window,
577 cx: &mut Context<Self>,
578 ) -> impl IntoElement {
579 let settings = AgentSettings::get_global(cx);
580
581 let profile_name = settings
582 .profiles
583 .get(&mode.profile_id)
584 .map(|profile| profile.name.clone())
585 .unwrap_or_else(|| "Unknown".into());
586
587 let icon = match mode.profile_id.as_str() {
588 "write" => IconName::Pencil,
589 "ask" => IconName::Chat,
590 _ => IconName::UserRoundPen,
591 };
592
593 Navigable::new(
594 div()
595 .track_focus(&self.focus_handle(cx))
596 .size_full()
597 .child(ProfileModalHeader::new(profile_name, Some(icon)))
598 .child(
599 v_flex()
600 .pb_1()
601 .child(ListSeparator)
602 .child(
603 div()
604 .id("fork-profile")
605 .track_focus(&mode.fork_profile.focus_handle)
606 .on_action({
607 let profile_id = mode.profile_id.clone();
608 cx.listener(move |this, _: &menu::Confirm, window, cx| {
609 this.new_profile(Some(profile_id.clone()), window, cx);
610 })
611 })
612 .child(
613 ListItem::new("fork-profile")
614 .toggle_state(
615 mode.fork_profile
616 .focus_handle
617 .contains_focused(window, cx),
618 )
619 .inset(true)
620 .spacing(ListItemSpacing::Sparse)
621 .start_slot(
622 Icon::new(IconName::Scissors)
623 .size(IconSize::Small)
624 .color(Color::Muted),
625 )
626 .child(Label::new("Fork Profile"))
627 .on_click({
628 let profile_id = mode.profile_id.clone();
629 cx.listener(move |this, _, window, cx| {
630 this.new_profile(
631 Some(profile_id.clone()),
632 window,
633 cx,
634 );
635 })
636 }),
637 ),
638 )
639 .child(
640 div()
641 .id("configure-default-model")
642 .track_focus(&mode.configure_default_model.focus_handle)
643 .on_action({
644 let profile_id = mode.profile_id.clone();
645 cx.listener(move |this, _: &menu::Confirm, window, cx| {
646 this.configure_default_model(
647 profile_id.clone(),
648 window,
649 cx,
650 );
651 })
652 })
653 .child(
654 ListItem::new("model-item")
655 .toggle_state(
656 mode.configure_default_model
657 .focus_handle
658 .contains_focused(window, cx),
659 )
660 .inset(true)
661 .spacing(ListItemSpacing::Sparse)
662 .start_slot(
663 Icon::new(IconName::ZedAssistant)
664 .size(IconSize::Small)
665 .color(Color::Muted),
666 )
667 .child(Label::new("Configure Default Model"))
668 .on_click({
669 let profile_id = mode.profile_id.clone();
670 cx.listener(move |this, _, window, cx| {
671 this.configure_default_model(
672 profile_id.clone(),
673 window,
674 cx,
675 );
676 })
677 }),
678 ),
679 )
680 .child(
681 div()
682 .id("configure-builtin-tools")
683 .track_focus(&mode.configure_tools.focus_handle)
684 .on_action({
685 let profile_id = mode.profile_id.clone();
686 cx.listener(move |this, _: &menu::Confirm, window, cx| {
687 this.configure_builtin_tools(
688 profile_id.clone(),
689 window,
690 cx,
691 );
692 })
693 })
694 .child(
695 ListItem::new("configure-builtin-tools-item")
696 .toggle_state(
697 mode.configure_tools
698 .focus_handle
699 .contains_focused(window, cx),
700 )
701 .inset(true)
702 .spacing(ListItemSpacing::Sparse)
703 .start_slot(
704 Icon::new(IconName::Settings)
705 .size(IconSize::Small)
706 .color(Color::Muted),
707 )
708 .child(Label::new("Configure Built-in Tools"))
709 .on_click({
710 let profile_id = mode.profile_id.clone();
711 cx.listener(move |this, _, window, cx| {
712 this.configure_builtin_tools(
713 profile_id.clone(),
714 window,
715 cx,
716 );
717 })
718 }),
719 ),
720 )
721 .child(
722 div()
723 .id("configure-mcps")
724 .track_focus(&mode.configure_mcps.focus_handle)
725 .on_action({
726 let profile_id = mode.profile_id.clone();
727 cx.listener(move |this, _: &menu::Confirm, window, cx| {
728 this.configure_mcp_tools(profile_id.clone(), window, cx);
729 })
730 })
731 .child(
732 ListItem::new("configure-mcp-tools")
733 .toggle_state(
734 mode.configure_mcps
735 .focus_handle
736 .contains_focused(window, cx),
737 )
738 .inset(true)
739 .spacing(ListItemSpacing::Sparse)
740 .start_slot(
741 Icon::new(IconName::ToolHammer)
742 .size(IconSize::Small)
743 .color(Color::Muted),
744 )
745 .child(Label::new("Configure MCP Tools"))
746 .on_click({
747 let profile_id = mode.profile_id.clone();
748 cx.listener(move |this, _, window, cx| {
749 this.configure_mcp_tools(
750 profile_id.clone(),
751 window,
752 cx,
753 );
754 })
755 }),
756 ),
757 )
758 .child(ListSeparator)
759 .child(
760 div()
761 .id("cancel-item")
762 .track_focus(&mode.cancel_item.focus_handle)
763 .on_action({
764 cx.listener(move |this, _: &menu::Confirm, window, cx| {
765 this.cancel(window, cx);
766 })
767 })
768 .child(
769 ListItem::new("cancel-item")
770 .toggle_state(
771 mode.cancel_item
772 .focus_handle
773 .contains_focused(window, cx),
774 )
775 .inset(true)
776 .spacing(ListItemSpacing::Sparse)
777 .start_slot(
778 Icon::new(IconName::ArrowLeft)
779 .size(IconSize::Small)
780 .color(Color::Muted),
781 )
782 .child(Label::new("Go Back"))
783 .end_slot(
784 div().child(
785 KeyBinding::for_action_in(
786 &menu::Cancel,
787 &self.focus_handle,
788 cx,
789 )
790 .size(rems_from_px(12.)),
791 ),
792 )
793 .on_click({
794 cx.listener(move |this, _, window, cx| {
795 this.cancel(window, cx);
796 })
797 }),
798 ),
799 ),
800 )
801 .into_any_element(),
802 )
803 .entry(mode.fork_profile)
804 .entry(mode.configure_default_model)
805 .entry(mode.configure_tools)
806 .entry(mode.configure_mcps)
807 .entry(mode.cancel_item)
808 }
809}
810
811impl Render for ManageProfilesModal {
812 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
813 let settings = AgentSettings::get_global(cx);
814
815 let go_back_item = div()
816 .id("cancel-item")
817 .track_focus(&self.focus_handle)
818 .on_action({
819 cx.listener(move |this, _: &menu::Confirm, window, cx| {
820 this.cancel(window, cx);
821 })
822 })
823 .child(
824 ListItem::new("cancel-item")
825 .toggle_state(self.focus_handle.contains_focused(window, cx))
826 .inset(true)
827 .spacing(ListItemSpacing::Sparse)
828 .start_slot(
829 Icon::new(IconName::ArrowLeft)
830 .size(IconSize::Small)
831 .color(Color::Muted),
832 )
833 .child(Label::new("Go Back"))
834 .end_slot(
835 div().child(
836 KeyBinding::for_action_in(&menu::Cancel, &self.focus_handle, cx)
837 .size(rems_from_px(12.)),
838 ),
839 )
840 .on_click({
841 cx.listener(move |this, _, window, cx| {
842 this.cancel(window, cx);
843 })
844 }),
845 );
846
847 div()
848 .elevation_3(cx)
849 .w(rems(34.))
850 .key_context("ManageProfilesModal")
851 .on_action(cx.listener(|this, _: &menu::Cancel, window, cx| this.cancel(window, cx)))
852 .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| this.confirm(window, cx)))
853 .capture_any_mouse_down(cx.listener(|this, _, window, cx| {
854 this.focus_handle(cx).focus(window);
855 }))
856 .on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
857 .child(match &self.mode {
858 Mode::ChooseProfile(mode) => self
859 .render_choose_profile(mode.clone(), window, cx)
860 .into_any_element(),
861 Mode::NewProfile(mode) => self
862 .render_new_profile(mode.clone(), window, cx)
863 .into_any_element(),
864 Mode::ViewProfile(mode) => self
865 .render_view_profile(mode.clone(), window, cx)
866 .into_any_element(),
867 Mode::ConfigureTools {
868 profile_id,
869 tool_picker,
870 ..
871 } => {
872 let profile_name = settings
873 .profiles
874 .get(profile_id)
875 .map(|profile| profile.name.clone())
876 .unwrap_or_else(|| "Unknown".into());
877
878 v_flex()
879 .pb_1()
880 .child(ProfileModalHeader::new(
881 format!("{profile_name} — Configure Built-in Tools"),
882 Some(IconName::Cog),
883 ))
884 .child(ListSeparator)
885 .child(tool_picker.clone())
886 .child(ListSeparator)
887 .child(go_back_item)
888 .into_any_element()
889 }
890 Mode::ConfigureDefaultModel {
891 profile_id,
892 model_picker,
893 ..
894 } => {
895 let profile_name = settings
896 .profiles
897 .get(profile_id)
898 .map(|profile| profile.name.clone())
899 .unwrap_or_else(|| "Unknown".into());
900
901 v_flex()
902 .pb_1()
903 .child(ProfileModalHeader::new(
904 format!("{profile_name} — Configure Default Model"),
905 Some(IconName::Ai),
906 ))
907 .child(ListSeparator)
908 .child(v_flex().w(rems(34.)).child(model_picker.clone()))
909 .child(ListSeparator)
910 .child(go_back_item)
911 .into_any_element()
912 }
913 Mode::ConfigureMcps {
914 profile_id,
915 tool_picker,
916 ..
917 } => {
918 let profile_name = settings
919 .profiles
920 .get(profile_id)
921 .map(|profile| profile.name.clone())
922 .unwrap_or_else(|| "Unknown".into());
923
924 v_flex()
925 .pb_1()
926 .child(ProfileModalHeader::new(
927 format!("{profile_name} — Configure MCP Tools"),
928 Some(IconName::ToolHammer),
929 ))
930 .child(ListSeparator)
931 .child(tool_picker.clone())
932 .child(ListSeparator)
933 .child(go_back_item)
934 .into_any_element()
935 }
936 })
937 }
938}