model_selector_popover.rs

  1use std::rc::Rc;
  2use std::sync::Arc;
  3
  4use acp_thread::{AgentModelInfo, AgentModelSelector};
  5use agent_servers::AgentServer;
  6use agent_settings::AgentSettings;
  7use fs::Fs;
  8use gpui::{Entity, FocusHandle};
  9use language_model::IconOrSvg;
 10use picker::popover_menu::PickerPopoverMenu;
 11use settings::Settings as _;
 12use ui::{ButtonLike, KeyBinding, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
 13use zed_actions::agent::ToggleModelSelector;
 14
 15use crate::CycleFavoriteModels;
 16use crate::acp::{AcpModelSelector, model_selector::acp_model_selector};
 17
 18pub struct AcpModelSelectorPopover {
 19    selector: Entity<AcpModelSelector>,
 20    menu_handle: PopoverMenuHandle<AcpModelSelector>,
 21    focus_handle: FocusHandle,
 22}
 23
 24impl AcpModelSelectorPopover {
 25    pub(crate) fn new(
 26        selector: Rc<dyn AgentModelSelector>,
 27        agent_server: Rc<dyn AgentServer>,
 28        fs: Arc<dyn Fs>,
 29        menu_handle: PopoverMenuHandle<AcpModelSelector>,
 30        focus_handle: FocusHandle,
 31        window: &mut Window,
 32        cx: &mut Context<Self>,
 33    ) -> Self {
 34        let focus_handle_clone = focus_handle.clone();
 35        Self {
 36            selector: cx.new(move |cx| {
 37                acp_model_selector(
 38                    selector,
 39                    agent_server,
 40                    fs,
 41                    focus_handle_clone.clone(),
 42                    window,
 43                    cx,
 44                )
 45            }),
 46            menu_handle,
 47            focus_handle,
 48        }
 49    }
 50
 51    pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
 52        self.menu_handle.toggle(window, cx);
 53    }
 54
 55    pub fn active_model<'a>(&self, cx: &'a App) -> Option<&'a AgentModelInfo> {
 56        self.selector.read(cx).delegate.active_model()
 57    }
 58
 59    pub fn cycle_favorite_models(&self, window: &mut Window, cx: &mut Context<Self>) {
 60        self.selector.update(cx, |selector, cx| {
 61            selector.delegate.cycle_favorite_models(window, cx);
 62        });
 63    }
 64}
 65
 66impl Render for AcpModelSelectorPopover {
 67    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 68        let model = self.selector.read(cx).delegate.active_model();
 69        let model_name = model
 70            .as_ref()
 71            .map(|model| model.name.clone())
 72            .unwrap_or_else(|| SharedString::from("Select a Model"));
 73
 74        let model_icon = model.as_ref().and_then(|model| model.icon.clone());
 75
 76        let focus_handle = self.focus_handle.clone();
 77
 78        let (color, icon) = if self.menu_handle.is_deployed() {
 79            (Color::Accent, IconName::ChevronUp)
 80        } else {
 81            (Color::Muted, IconName::ChevronDown)
 82        };
 83
 84        let tooltip = Tooltip::element({
 85            move |_, cx| {
 86                let focus_handle = focus_handle.clone();
 87                let should_show_cycle_row = !AgentSettings::get_global(cx)
 88                    .favorite_model_ids()
 89                    .is_empty();
 90
 91                v_flex()
 92                    .gap_1()
 93                    .child(
 94                        h_flex()
 95                            .gap_2()
 96                            .justify_between()
 97                            .child(Label::new("Change Model"))
 98                            .child(KeyBinding::for_action_in(
 99                                &ToggleModelSelector,
100                                &focus_handle,
101                                cx,
102                            )),
103                    )
104                    .when(should_show_cycle_row, |this| {
105                        this.child(
106                            h_flex()
107                                .pt_1()
108                                .gap_2()
109                                .border_t_1()
110                                .border_color(cx.theme().colors().border_variant)
111                                .justify_between()
112                                .child(Label::new("Cycle Favorited Models"))
113                                .child(KeyBinding::for_action_in(
114                                    &CycleFavoriteModels,
115                                    &focus_handle,
116                                    cx,
117                                )),
118                        )
119                    })
120                    .into_any()
121            }
122        });
123
124        PickerPopoverMenu::new(
125            self.selector.clone(),
126            ButtonLike::new("active-model")
127                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
128                .when_some(model_icon, |this, icon| {
129                    this.child(
130                        match icon {
131                            IconOrSvg::Svg(path) => Icon::from_external_svg(path),
132                            IconOrSvg::Icon(icon_name) => Icon::new(icon_name),
133                        }
134                        .color(color)
135                        .size(IconSize::XSmall),
136                    )
137                })
138                .child(
139                    Label::new(model_name)
140                        .color(color)
141                        .size(LabelSize::Small)
142                        .ml_0p5(),
143                )
144                .child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
145            tooltip,
146            gpui::Corner::BottomRight,
147            cx,
148        )
149        .with_handle(self.menu_handle.clone())
150        .render(window, cx)
151    }
152}