model_selector_popover.rs

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