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