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