model_selector_popover.rs

  1use std::rc::Rc;
  2use std::sync::Arc;
  3
  4use acp_thread::{AgentModelIcon, AgentModelInfo, AgentModelSelector};
  5use fs::Fs;
  6use gpui::{AnyView, Entity, FocusHandle};
  7use picker::popover_menu::PickerPopoverMenu;
  8use ui::{PopoverMenuHandle, Tooltip, prelude::*};
  9
 10use crate::ui::ModelSelectorTooltip;
 11use crate::{ModelSelector, model_selector::acp_model_selector};
 12
 13pub struct ModelSelectorPopover {
 14    selector: Entity<ModelSelector>,
 15    menu_handle: PopoverMenuHandle<ModelSelector>,
 16    disabled: bool,
 17}
 18
 19impl ModelSelectorPopover {
 20    pub(crate) fn new(
 21        selector: Rc<dyn AgentModelSelector>,
 22        agent_server: Rc<dyn agent_servers::AgentServer>,
 23        fs: Arc<dyn Fs>,
 24        menu_handle: PopoverMenuHandle<ModelSelector>,
 25        focus_handle: FocusHandle,
 26        window: &mut Window,
 27        cx: &mut Context<Self>,
 28    ) -> Self {
 29        Self {
 30            selector: cx.new(move |cx| {
 31                acp_model_selector(selector, agent_server, fs, focus_handle.clone(), window, cx)
 32            }),
 33            menu_handle,
 34            disabled: false,
 35        }
 36    }
 37
 38    pub fn set_disabled(&mut self, disabled: bool) {
 39        self.disabled = disabled;
 40    }
 41
 42    pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
 43        if self.disabled {
 44            return;
 45        }
 46        self.menu_handle.toggle(window, cx);
 47    }
 48
 49    pub fn active_model<'a>(&self, cx: &'a App) -> Option<&'a AgentModelInfo> {
 50        self.selector.read(cx).delegate.active_model()
 51    }
 52
 53    pub fn cycle_favorite_models(&self, window: &mut Window, cx: &mut Context<Self>) {
 54        if self.disabled {
 55            return;
 56        }
 57        self.selector.update(cx, |selector, cx| {
 58            selector.delegate.cycle_favorite_models(window, cx);
 59        });
 60    }
 61}
 62
 63impl Render for ModelSelectorPopover {
 64    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 65        let selector = self.selector.read(cx);
 66        let model = selector.delegate.active_model();
 67        let model_name = model
 68            .as_ref()
 69            .map(|model| model.name.clone())
 70            .unwrap_or_else(|| SharedString::from("Select a Model"));
 71
 72        let model_icon = model.as_ref().and_then(|model| model.icon.clone());
 73
 74        let (color, icon) = if self.menu_handle.is_deployed() {
 75            (Color::Accent, IconName::ChevronUp)
 76        } else if self.disabled {
 77            (Color::Disabled, IconName::ChevronDown)
 78        } else {
 79            (Color::Muted, IconName::ChevronDown)
 80        };
 81
 82        let show_cycle_row = selector.delegate.favorites_count() > 1;
 83        let disabled = self.disabled;
 84
 85        let tooltip: Box<dyn Fn(&mut Window, &mut App) -> AnyView> = if disabled {
 86            Box::new(Tooltip::text("Disabled until generation is done"))
 87        } else {
 88            Box::new(Tooltip::element({
 89                move |_, _cx| {
 90                    ModelSelectorTooltip::new()
 91                        .show_cycle_row(show_cycle_row)
 92                        .into_any_element()
 93                }
 94            }))
 95        };
 96
 97        PickerPopoverMenu::new(
 98            self.selector.clone(),
 99            Button::new("active-model", model_name)
100                .label_size(LabelSize::Small)
101                .color(color)
102                .disabled(self.disabled)
103                .when_some(model_icon, |this, icon| {
104                    this.start_icon(
105                        match icon {
106                            AgentModelIcon::Path(path) => Icon::from_external_svg(path),
107                            AgentModelIcon::Named(icon_name) => Icon::new(icon_name),
108                        }
109                        .color(color)
110                        .size(IconSize::XSmall),
111                    )
112                })
113                .end_icon(
114                    Icon::new(icon)
115                        .map(|this| {
116                            if self.disabled {
117                                this.color(Color::Disabled)
118                            } else {
119                                this.color(Color::Muted)
120                            }
121                        })
122                        .size(IconSize::XSmall),
123                ),
124            tooltip,
125            gpui::Corner::BottomRight,
126            cx,
127        )
128        .with_handle(self.menu_handle.clone())
129        .render(window, cx)
130    }
131}