1use std::rc::Rc;
2use std::sync::Arc;
3
4use acp_thread::{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);
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(Icon::new(icon).color(color).size(IconSize::XSmall))
129 })
130 .child(
131 Label::new(model_name)
132 .color(color)
133 .size(LabelSize::Small)
134 .ml_0p5(),
135 )
136 .child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
137 tooltip,
138 gpui::Corner::BottomRight,
139 cx,
140 )
141 .with_handle(self.menu_handle.clone())
142 .render(window, cx)
143 }
144}