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}