agent_model_selector.rs

  1use crate::{
  2    ModelUsageContext,
  3    language_model_selector::{LanguageModelSelector, language_model_selector},
  4};
  5use fs::Fs;
  6use gpui::{Entity, FocusHandle, SharedString};
  7use language_model::IconOrSvg;
  8use picker::popover_menu::PickerPopoverMenu;
  9use settings::update_settings_file;
 10use std::sync::Arc;
 11use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
 12use zed_actions::agent::ToggleModelSelector;
 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
 86impl Render for AgentModelSelector {
 87    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 88        let model = self.selector.read(cx).delegate.active_model(cx);
 89        let model_name = model
 90            .as_ref()
 91            .map(|model| model.model.name().0)
 92            .unwrap_or_else(|| SharedString::from("Select a Model"));
 93
 94        let provider_icon = model.as_ref().map(|model| model.provider.icon());
 95        let color = if self.menu_handle.is_deployed() {
 96            Color::Accent
 97        } else {
 98            Color::Muted
 99        };
100
101        let focus_handle = self.focus_handle.clone();
102
103        PickerPopoverMenu::new(
104            self.selector.clone(),
105            ButtonLike::new("active-model")
106                .when_some(provider_icon, |this, icon| {
107                    this.child(
108                        match icon {
109                            IconOrSvg::Svg(path) => Icon::from_external_svg(path),
110                            IconOrSvg::Icon(name) => Icon::new(name),
111                        }
112                        .color(color)
113                        .size(IconSize::XSmall),
114                    )
115                })
116                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
117                .child(
118                    Label::new(model_name)
119                        .color(color)
120                        .size(LabelSize::Small)
121                        .ml_0p5(),
122                )
123                .child(
124                    Icon::new(IconName::ChevronDown)
125                        .color(color)
126                        .size(IconSize::XSmall),
127                ),
128            move |_window, cx| {
129                Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
130            },
131            gpui::Corner::TopRight,
132            cx,
133        )
134        .with_handle(self.menu_handle.clone())
135        .offset(gpui::Point {
136            x: px(0.0),
137            y: px(2.0),
138        })
139        .render(window, cx)
140    }
141}