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