Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:07:05.486Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

manage_profiles_modal.rs

1079 lines · 45.6 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.