ui/components/ai/
agent_model_selector.rs1use std::sync::Arc;
2
3use gpui::SharedString;
4
5use crate::prelude::*;
6use crate::{ContextMenu, DropdownMenu, DropdownStyle};
7
8#[derive(IntoElement, RegisterComponent)]
14pub struct AgentModelSelector {
15 id: ElementId,
16 current_model_id: Option<SharedString>,
17 available_model_ids: Vec<SharedString>,
18 on_select: Option<Arc<dyn Fn(SharedString, &mut Window, &mut App) + 'static>>,
19}
20
21impl AgentModelSelector {
22 pub fn new(
23 id: impl Into<ElementId>,
24 current_model_id: Option<&str>,
25 available_model_ids: &[String],
26 ) -> Self {
27 Self {
28 id: id.into(),
29 current_model_id: current_model_id.map(SharedString::from),
30 available_model_ids: available_model_ids
31 .iter()
32 .map(|id| SharedString::from(id.clone()))
33 .collect(),
34 on_select: None,
35 }
36 }
37
38 pub fn on_select(
39 mut self,
40 handler: impl Fn(SharedString, &mut Window, &mut App) + 'static,
41 ) -> Self {
42 self.on_select = Some(Arc::new(handler));
43 self
44 }
45}
46
47impl RenderOnce for AgentModelSelector {
48 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
49 let label = self
50 .current_model_id
51 .clone()
52 .unwrap_or_else(|| "Select model".into());
53 let current = self.current_model_id.clone();
54 let on_select = self.on_select.clone();
55 let available_model_ids = self.available_model_ids.clone();
56
57 let menu = ContextMenu::build(window, cx, move |mut menu, _window, _cx| {
58 for model_id in &available_model_ids {
59 let is_current = current.as_ref() == Some(model_id);
60 let model_id = model_id.clone();
61 let on_select = on_select.clone();
62 menu = menu.toggleable_entry(
63 model_id.clone(),
64 is_current,
65 IconPosition::End,
66 None,
67 move |window, cx| {
68 if let Some(on_select) = &on_select {
69 on_select(model_id.clone(), window, cx);
70 }
71 },
72 );
73 }
74 menu
75 });
76
77 DropdownMenu::new(self.id, label, menu).style(DropdownStyle::Subtle)
78 }
79}
80
81impl Component for AgentModelSelector {
82 fn scope() -> ComponentScope {
83 ComponentScope::Agent
84 }
85
86 fn name() -> &'static str {
87 "AgentModelSelector"
88 }
89
90 fn description() -> Option<&'static str> {
91 Some(
92 "A dropdown for picking the active model for an agent thread. \
93 The caller supplies the current model id and the full list of \
94 available model ids; no data fetching happens here.",
95 )
96 }
97
98 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
99 let models = vec![
100 "claude-opus-4.6".to_string(),
101 "claude-sonnet-4.6".to_string(),
102 "gpt-5".to_string(),
103 ];
104
105 Some(
106 v_flex()
107 .gap_4()
108 .child(single_example(
109 "With selection",
110 AgentModelSelector::new("model-selector-1", Some("claude-sonnet-4.6"), &models)
111 .into_any_element(),
112 ))
113 .child(single_example(
114 "No selection",
115 AgentModelSelector::new("model-selector-2", None, &models).into_any_element(),
116 ))
117 .into_any_element(),
118 )
119 }
120}