1use gpui::{Action, ClickEvent, FocusHandle, prelude::*};
2use ui::{ElevationIndex, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
3use zed_actions::agent::ToggleModelSelector;
4
5use crate::CycleFavoriteModels;
6
7enum ModelIcon {
8 Name(IconName),
9 Path(SharedString),
10}
11
12#[derive(IntoElement)]
13pub struct ModelSelectorHeader {
14 title: SharedString,
15 has_border: bool,
16}
17
18impl ModelSelectorHeader {
19 pub fn new(title: impl Into<SharedString>, has_border: bool) -> Self {
20 Self {
21 title: title.into(),
22 has_border,
23 }
24 }
25}
26
27impl RenderOnce for ModelSelectorHeader {
28 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
29 div()
30 .px_2()
31 .pb_1()
32 .when(self.has_border, |this| {
33 this.mt_1()
34 .pt_2()
35 .border_t_1()
36 .border_color(cx.theme().colors().border_variant)
37 })
38 .child(
39 Label::new(self.title)
40 .size(LabelSize::XSmall)
41 .color(Color::Muted),
42 )
43 }
44}
45
46#[derive(IntoElement)]
47pub struct ModelSelectorListItem {
48 index: usize,
49 title: SharedString,
50 icon: Option<ModelIcon>,
51 is_selected: bool,
52 is_focused: bool,
53 is_favorite: bool,
54 on_toggle_favorite: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
55}
56
57impl ModelSelectorListItem {
58 pub fn new(index: usize, title: impl Into<SharedString>) -> Self {
59 Self {
60 index,
61 title: title.into(),
62 icon: None,
63 is_selected: false,
64 is_focused: false,
65 is_favorite: false,
66 on_toggle_favorite: None,
67 }
68 }
69
70 pub fn icon(mut self, icon: IconName) -> Self {
71 self.icon = Some(ModelIcon::Name(icon));
72 self
73 }
74
75 pub fn icon_path(mut self, path: SharedString) -> Self {
76 self.icon = Some(ModelIcon::Path(path));
77 self
78 }
79
80 pub fn is_selected(mut self, is_selected: bool) -> Self {
81 self.is_selected = is_selected;
82 self
83 }
84
85 pub fn is_focused(mut self, is_focused: bool) -> Self {
86 self.is_focused = is_focused;
87 self
88 }
89
90 pub fn is_favorite(mut self, is_favorite: bool) -> Self {
91 self.is_favorite = is_favorite;
92 self
93 }
94
95 pub fn on_toggle_favorite(
96 mut self,
97 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
98 ) -> Self {
99 self.on_toggle_favorite = Some(Box::new(handler));
100 self
101 }
102}
103
104impl RenderOnce for ModelSelectorListItem {
105 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
106 let model_icon_color = if self.is_selected {
107 Color::Accent
108 } else {
109 Color::Muted
110 };
111
112 let is_favorite = self.is_favorite;
113
114 ListItem::new(self.index)
115 .inset(true)
116 .spacing(ListItemSpacing::Sparse)
117 .toggle_state(self.is_focused)
118 .child(
119 h_flex()
120 .w_full()
121 .gap_1p5()
122 .when_some(self.icon, |this, icon| {
123 this.child(
124 match icon {
125 ModelIcon::Name(icon_name) => Icon::new(icon_name),
126 ModelIcon::Path(icon_path) => Icon::from_external_svg(icon_path),
127 }
128 .color(model_icon_color)
129 .size(IconSize::Small),
130 )
131 })
132 .child(Label::new(self.title).truncate()),
133 )
134 .end_slot(div().pr_2().when(self.is_selected, |this| {
135 this.child(Icon::new(IconName::Check).color(Color::Accent))
136 }))
137 .end_hover_slot(div().pr_1p5().when_some(self.on_toggle_favorite, {
138 |this, handle_click| {
139 let (icon, color, tooltip) = if is_favorite {
140 (IconName::StarFilled, Color::Accent, "Unfavorite Model")
141 } else {
142 (IconName::Star, Color::Default, "Favorite Model")
143 };
144 this.child(
145 IconButton::new(("toggle-favorite", self.index), icon)
146 .layer(ElevationIndex::ElevatedSurface)
147 .icon_color(color)
148 .icon_size(IconSize::Small)
149 .tooltip(Tooltip::text(tooltip))
150 .on_click(move |event, window, cx| (handle_click)(event, window, cx)),
151 )
152 }
153 }))
154 }
155}
156
157#[derive(IntoElement)]
158pub struct ModelSelectorFooter {
159 action: Box<dyn Action>,
160 focus_handle: FocusHandle,
161}
162
163impl ModelSelectorFooter {
164 pub fn new(action: Box<dyn Action>, focus_handle: FocusHandle) -> Self {
165 Self {
166 action,
167 focus_handle,
168 }
169 }
170}
171
172impl RenderOnce for ModelSelectorFooter {
173 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
174 let action = self.action;
175 let focus_handle = self.focus_handle;
176
177 h_flex()
178 .w_full()
179 .p_1p5()
180 .border_t_1()
181 .border_color(cx.theme().colors().border_variant)
182 .child(
183 Button::new("configure", "Configure")
184 .full_width()
185 .style(ButtonStyle::Outlined)
186 .key_binding(
187 KeyBinding::for_action_in(action.as_ref(), &focus_handle, cx)
188 .map(|kb| kb.size(rems_from_px(12.))),
189 )
190 .on_click(move |_, window, cx| {
191 window.dispatch_action(action.boxed_clone(), cx);
192 }),
193 )
194 }
195}
196
197#[derive(IntoElement)]
198pub struct ModelSelectorTooltip {
199 focus_handle: FocusHandle,
200 show_cycle_row: bool,
201}
202
203impl ModelSelectorTooltip {
204 pub fn new(focus_handle: FocusHandle) -> Self {
205 Self {
206 focus_handle,
207 show_cycle_row: true,
208 }
209 }
210
211 pub fn show_cycle_row(mut self, show: bool) -> Self {
212 self.show_cycle_row = show;
213 self
214 }
215}
216
217impl RenderOnce for ModelSelectorTooltip {
218 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
219 v_flex()
220 .gap_1()
221 .child(
222 h_flex()
223 .gap_2()
224 .justify_between()
225 .child(Label::new("Change Model"))
226 .child(KeyBinding::for_action_in(
227 &ToggleModelSelector,
228 &self.focus_handle,
229 cx,
230 )),
231 )
232 .when(self.show_cycle_row, |this| {
233 this.child(
234 h_flex()
235 .pt_1()
236 .gap_2()
237 .border_t_1()
238 .border_color(cx.theme().colors().border_variant)
239 .justify_between()
240 .child(Label::new("Cycle Favorited Models"))
241 .child(KeyBinding::for_action_in(
242 &CycleFavoriteModels,
243 &self.focus_handle,
244 cx,
245 )),
246 )
247 })
248 }
249}