model_selector_popover.rs

  1use std::rc::Rc;
  2use std::sync::Arc;
  3
  4use acp_thread::{AgentModelIcon, AgentModelInfo, AgentModelSelector};
  5use agent_servers::AgentServer;
  6use fs::Fs;
  7use gpui::{Entity, FocusHandle};
  8use picker::popover_menu::PickerPopoverMenu;
  9use ui::{
 10    ButtonLike, Context, IntoElement, PopoverMenuHandle, SharedString, TintColor, Tooltip, Window,
 11    prelude::*,
 12};
 13use zed_actions::agent::ToggleModelSelector;
 14
 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
 59impl Render for AcpModelSelectorPopover {
 60    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 61        let model = self.selector.read(cx).delegate.active_model();
 62        let model_name = model
 63            .as_ref()
 64            .map(|model| model.name.clone())
 65            .unwrap_or_else(|| SharedString::from("Select a Model"));
 66
 67        let model_icon = model.as_ref().and_then(|model| model.icon.clone());
 68
 69        let focus_handle = self.focus_handle.clone();
 70
 71        let (color, icon) = if self.menu_handle.is_deployed() {
 72            (Color::Accent, IconName::ChevronUp)
 73        } else {
 74            (Color::Muted, IconName::ChevronDown)
 75        };
 76
 77        PickerPopoverMenu::new(
 78            self.selector.clone(),
 79            ButtonLike::new("active-model")
 80                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
 81                .when_some(model_icon, |this, icon| {
 82                    this.child(
 83                        match icon {
 84                            AgentModelIcon::Path(path) => Icon::from_external_svg(path),
 85                            AgentModelIcon::Named(icon_name) => Icon::new(icon_name),
 86                        }
 87                        .color(color)
 88                        .size(IconSize::XSmall),
 89                    )
 90                })
 91                .child(
 92                    Label::new(model_name)
 93                        .color(color)
 94                        .size(LabelSize::Small)
 95                        .ml_0p5(),
 96                )
 97                .child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
 98            move |_window, cx| {
 99                Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
100            },
101            gpui::Corner::BottomRight,
102            cx,
103        )
104        .with_handle(self.menu_handle.clone())
105        .render(window, cx)
106    }
107}