agent_model_selector.rs

  1use crate::{
  2    ModelUsageContext,
  3    language_model_selector::{LanguageModelSelector, language_model_selector},
  4    ui::ModelSelectorTooltip,
  5};
  6use fs::Fs;
  7use gpui::{Entity, FocusHandle, SharedString};
  8use language_model::IconOrSvg;
  9use picker::popover_menu::PickerPopoverMenu;
 10use settings::update_settings_file;
 11use std::sync::Arc;
 12use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
 13
 14pub struct AgentModelSelector {
 15    selector: Entity<LanguageModelSelector>,
 16    menu_handle: PopoverMenuHandle<LanguageModelSelector>,
 17    focus_handle: FocusHandle,
 18}
 19
 20impl AgentModelSelector {
 21    pub(crate) fn new(
 22        fs: Arc<dyn Fs>,
 23        menu_handle: PopoverMenuHandle<LanguageModelSelector>,
 24        focus_handle: FocusHandle,
 25        model_usage_context: ModelUsageContext,
 26        window: &mut Window,
 27        cx: &mut Context<Self>,
 28    ) -> Self {
 29        let focus_handle_clone = focus_handle.clone();
 30
 31        Self {
 32            selector: cx.new(move |cx| {
 33                language_model_selector(
 34                    {
 35                        let model_context = model_usage_context.clone();
 36                        move |cx| model_context.configured_model(cx)
 37                    },
 38                    {
 39                        let fs = fs.clone();
 40                        move |model, cx| {
 41                            let provider = model.provider_id().0.to_string();
 42                            let model_id = model.id().0.to_string();
 43                            match &model_usage_context {
 44                                ModelUsageContext::InlineAssistant => {
 45                                    update_settings_file(fs.clone(), cx, move |settings, _cx| {
 46                                        settings
 47                                            .agent
 48                                            .get_or_insert_default()
 49                                            .set_inline_assistant_model(provider.clone(), model_id);
 50                                    });
 51                                }
 52                            }
 53                        }
 54                    },
 55                    {
 56                        let fs = fs.clone();
 57                        move |model, should_be_favorite, cx| {
 58                            crate::favorite_models::toggle_in_settings(
 59                                model,
 60                                should_be_favorite,
 61                                fs.clone(),
 62                                cx,
 63                            );
 64                        }
 65                    },
 66                    true, // Use popover styles for picker
 67                    focus_handle_clone,
 68                    window,
 69                    cx,
 70                )
 71            }),
 72            menu_handle,
 73            focus_handle,
 74        }
 75    }
 76
 77    pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
 78        self.menu_handle.toggle(window, cx);
 79    }
 80
 81    pub fn active_model(&self, cx: &App) -> Option<language_model::ConfiguredModel> {
 82        self.selector.read(cx).delegate.active_model(cx)
 83    }
 84
 85    pub fn cycle_favorite_models(&self, window: &mut Window, cx: &mut Context<Self>) {
 86        self.selector.update(cx, |selector, cx| {
 87            selector.delegate.cycle_favorite_models(window, cx);
 88        });
 89    }
 90}
 91
 92impl Render for AgentModelSelector {
 93    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 94        let model = self.selector.read(cx).delegate.active_model(cx);
 95        let model_name = model
 96            .as_ref()
 97            .map(|model| model.model.name().0)
 98            .unwrap_or_else(|| SharedString::from("Select a Model"));
 99
100        let provider_icon = model.as_ref().map(|model| model.provider.icon());
101        let color = if self.menu_handle.is_deployed() {
102            Color::Accent
103        } else {
104            Color::Muted
105        };
106
107        let show_cycle_row = self.selector.read(cx).delegate.favorites_count() > 1;
108
109        let focus_handle = self.focus_handle.clone();
110
111        let tooltip = Tooltip::element({
112            move |_, _cx| {
113                ModelSelectorTooltip::new(focus_handle.clone())
114                    .show_cycle_row(show_cycle_row)
115                    .into_any_element()
116            }
117        });
118
119        PickerPopoverMenu::new(
120            self.selector.clone(),
121            ButtonLike::new("active-model")
122                .when_some(provider_icon, |this, icon| {
123                    this.child(
124                        match icon {
125                            IconOrSvg::Svg(path) => Icon::from_external_svg(path),
126                            IconOrSvg::Icon(name) => Icon::new(name),
127                        }
128                        .color(color)
129                        .size(IconSize::XSmall),
130                    )
131                })
132                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
133                .child(
134                    Label::new(model_name)
135                        .color(color)
136                        .size(LabelSize::Small)
137                        .ml_0p5(),
138                )
139                .child(
140                    Icon::new(IconName::ChevronDown)
141                        .color(color)
142                        .size(IconSize::XSmall),
143                ),
144            tooltip,
145            gpui::Corner::TopRight,
146            cx,
147        )
148        .with_handle(self.menu_handle.clone())
149        .offset(gpui::Point {
150            x: px(0.0),
151            y: px(2.0),
152        })
153        .render(window, cx)
154    }
155}