1use crate::{
2 ModelUsageContext,
3 language_model_selector::{LanguageModelSelector, language_model_selector},
4 ui::ModelSelectorTooltip,
5};
6use fs::Fs;
7use gpui::{Entity, FocusHandle, SharedString};
8use language_model::IconOrSvg;
9use picker::popover_menu::PickerPopoverMenu;
10use settings::update_settings_file;
11use std::sync::Arc;
12use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
13
14pub struct AgentModelSelector {
15 selector: Entity<LanguageModelSelector>,
16 menu_handle: PopoverMenuHandle<LanguageModelSelector>,
17 focus_handle: FocusHandle,
18}
19
20impl AgentModelSelector {
21 pub(crate) fn new(
22 fs: Arc<dyn Fs>,
23 menu_handle: PopoverMenuHandle<LanguageModelSelector>,
24 focus_handle: FocusHandle,
25 model_usage_context: ModelUsageContext,
26 window: &mut Window,
27 cx: &mut Context<Self>,
28 ) -> Self {
29 let focus_handle_clone = focus_handle.clone();
30
31 Self {
32 selector: cx.new(move |cx| {
33 language_model_selector(
34 {
35 let model_context = model_usage_context.clone();
36 move |cx| model_context.configured_model(cx)
37 },
38 {
39 let fs = fs.clone();
40 move |model, cx| {
41 let provider = model.provider_id().0.to_string();
42 let model_id = model.id().0.to_string();
43 match &model_usage_context {
44 ModelUsageContext::InlineAssistant => {
45 update_settings_file(fs.clone(), cx, move |settings, _cx| {
46 settings
47 .agent
48 .get_or_insert_default()
49 .set_inline_assistant_model(provider.clone(), model_id);
50 });
51 }
52 }
53 }
54 },
55 {
56 let fs = fs.clone();
57 move |model, should_be_favorite, cx| {
58 crate::favorite_models::toggle_in_settings(
59 model,
60 should_be_favorite,
61 fs.clone(),
62 cx,
63 );
64 }
65 },
66 true, // Use popover styles for picker
67 focus_handle_clone,
68 window,
69 cx,
70 )
71 }),
72 menu_handle,
73 focus_handle,
74 }
75 }
76
77 pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
78 self.menu_handle.toggle(window, cx);
79 }
80
81 pub fn active_model(&self, cx: &App) -> Option<language_model::ConfiguredModel> {
82 self.selector.read(cx).delegate.active_model(cx)
83 }
84
85 pub fn cycle_favorite_models(&self, window: &mut Window, cx: &mut Context<Self>) {
86 self.selector.update(cx, |selector, cx| {
87 selector.delegate.cycle_favorite_models(window, cx);
88 });
89 }
90}
91
92impl Render for AgentModelSelector {
93 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
94 let model = self.selector.read(cx).delegate.active_model(cx);
95 let model_name = model
96 .as_ref()
97 .map(|model| model.model.name().0)
98 .unwrap_or_else(|| SharedString::from("Select a Model"));
99
100 let provider_icon = model.as_ref().map(|model| model.provider.icon());
101 let color = if self.menu_handle.is_deployed() {
102 Color::Accent
103 } else {
104 Color::Muted
105 };
106
107 let show_cycle_row = self.selector.read(cx).delegate.favorites_count() > 1;
108
109 let focus_handle = self.focus_handle.clone();
110
111 let tooltip = Tooltip::element({
112 move |_, _cx| {
113 ModelSelectorTooltip::new(focus_handle.clone())
114 .show_cycle_row(show_cycle_row)
115 .into_any_element()
116 }
117 });
118
119 PickerPopoverMenu::new(
120 self.selector.clone(),
121 ButtonLike::new("active-model")
122 .when_some(provider_icon, |this, icon| {
123 this.child(
124 match icon {
125 IconOrSvg::Svg(path) => Icon::from_external_svg(path),
126 IconOrSvg::Icon(name) => Icon::new(name),
127 }
128 .color(color)
129 .size(IconSize::XSmall),
130 )
131 })
132 .selected_style(ButtonStyle::Tinted(TintColor::Accent))
133 .child(
134 Label::new(model_name)
135 .color(color)
136 .size(LabelSize::Small)
137 .ml_0p5(),
138 )
139 .child(
140 Icon::new(IconName::ChevronDown)
141 .color(color)
142 .size(IconSize::XSmall),
143 ),
144 tooltip,
145 gpui::Corner::TopRight,
146 cx,
147 )
148 .with_handle(self.menu_handle.clone())
149 .offset(gpui::Point {
150 x: px(0.0),
151 y: px(2.0),
152 })
153 .render(window, cx)
154 }
155}