model_selector_popover.rs

 1use std::rc::Rc;
 2use std::sync::Arc;
 3
 4use acp_thread::{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        Self {
34            selector: cx.new(move |cx| acp_model_selector(selector, agent_server, fs, window, cx)),
35            menu_handle,
36            focus_handle,
37        }
38    }
39
40    pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
41        self.menu_handle.toggle(window, cx);
42    }
43
44    pub fn active_model<'a>(&self, cx: &'a App) -> Option<&'a AgentModelInfo> {
45        self.selector.read(cx).delegate.active_model()
46    }
47}
48
49impl Render for AcpModelSelectorPopover {
50    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
51        let model = self.selector.read(cx).delegate.active_model();
52        let model_name = model
53            .as_ref()
54            .map(|model| model.name.clone())
55            .unwrap_or_else(|| SharedString::from("Select a Model"));
56
57        let model_icon = model.as_ref().and_then(|model| model.icon);
58
59        let focus_handle = self.focus_handle.clone();
60
61        let (color, icon) = if self.menu_handle.is_deployed() {
62            (Color::Accent, IconName::ChevronUp)
63        } else {
64            (Color::Muted, IconName::ChevronDown)
65        };
66
67        PickerPopoverMenu::new(
68            self.selector.clone(),
69            ButtonLike::new("active-model")
70                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
71                .when_some(model_icon, |this, icon| {
72                    this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
73                })
74                .child(
75                    Label::new(model_name)
76                        .color(color)
77                        .size(LabelSize::Small)
78                        .ml_0p5(),
79                )
80                .child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
81            move |_window, cx| {
82                Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
83            },
84            gpui::Corner::BottomRight,
85            cx,
86        )
87        .with_handle(self.menu_handle.clone())
88        .render(window, cx)
89    }
90}