language_model_selector: Don't recreate the `Picker` view each render (#21939)

Marshall Bowers created

While working on Assistant2, I noticed that the `LanguageModelSelector`
was recreating its `Picker` view on every single render.

This PR makes it so we create the view once and hold onto it in the
parent view.

Release Notes:

- N/A

Change summary

crates/assistant/src/assistant_panel.rs                       |  45 
crates/assistant/src/inline_assistant.rs                      |  76 
crates/assistant/src/terminal_inline_assistant.rs             |  31 
crates/assistant2/src/inline_assistant.rs                     |  76 
crates/assistant2/src/message_editor.rs                       |  17 
crates/assistant2/src/terminal_inline_assistant.rs            |  31 
crates/language_model_selector/src/language_model_selector.rs | 219 ++--
7 files changed, 259 insertions(+), 236 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -55,7 +55,7 @@ use language_model::{
     LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role,
     ZED_CLOUD_PROVIDER_ID,
 };
-use language_model_selector::{LanguageModelPickerDelegate, LanguageModelSelector};
+use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
 use multi_buffer::MultiBufferRow;
 use picker::{Picker, PickerDelegate};
 use project::lsp_store::LocalLspAdapterDelegate;
@@ -143,7 +143,7 @@ pub struct AssistantPanel {
     languages: Arc<LanguageRegistry>,
     fs: Arc<dyn Fs>,
     subscriptions: Vec<Subscription>,
-    model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
+    model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
     model_summary_editor: View<Editor>,
     authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
     configuration_subscription: Option<Subscription>,
@@ -341,11 +341,12 @@ impl AssistantPanel {
     ) -> Self {
         let model_selector_menu_handle = PopoverMenuHandle::default();
         let model_summary_editor = cx.new_view(Editor::single_line);
-        let context_editor_toolbar = cx.new_view(|_| {
+        let context_editor_toolbar = cx.new_view(|cx| {
             ContextEditorToolbarItem::new(
                 workspace,
                 model_selector_menu_handle.clone(),
                 model_summary_editor.clone(),
+                cx,
             )
         });
 
@@ -4455,23 +4456,36 @@ impl FollowableItem for ContextEditor {
 }
 
 pub struct ContextEditorToolbarItem {
-    fs: Arc<dyn Fs>,
     active_context_editor: Option<WeakView<ContextEditor>>,
     model_summary_editor: View<Editor>,
-    model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
+    language_model_selector: View<LanguageModelSelector>,
+    language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
 }
 
 impl ContextEditorToolbarItem {
     pub fn new(
         workspace: &Workspace,
-        model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
+        model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
         model_summary_editor: View<Editor>,
+        cx: &mut ViewContext<Self>,
     ) -> Self {
         Self {
-            fs: workspace.app_state().fs.clone(),
             active_context_editor: None,
             model_summary_editor,
-            model_selector_menu_handle,
+            language_model_selector: cx.new_view(|cx| {
+                let fs = workspace.app_state().fs.clone();
+                LanguageModelSelector::new(
+                    move |model, cx| {
+                        update_settings_file::<AssistantSettings>(
+                            fs.clone(),
+                            cx,
+                            move |settings, _| settings.set_model(model.clone()),
+                        );
+                    },
+                    cx,
+                )
+            }),
+            language_model_selector_menu_handle: model_selector_menu_handle,
         }
     }
 
@@ -4560,17 +4574,8 @@ impl Render for ContextEditorToolbarItem {
             //         .map(|remaining_items| format!("Files to scan: {}", remaining_items))
             // })
             .child(
-                LanguageModelSelector::new(
-                    {
-                        let fs = self.fs.clone();
-                        move |model, cx| {
-                            update_settings_file::<AssistantSettings>(
-                                fs.clone(),
-                                cx,
-                                move |settings, _| settings.set_model(model.clone()),
-                            );
-                        }
-                    },
+                LanguageModelSelectorPopoverMenu::new(
+                    self.language_model_selector.clone(),
                     ButtonLike::new("active-model")
                         .style(ButtonStyle::Subtle)
                         .child(
@@ -4616,7 +4621,7 @@ impl Render for ContextEditorToolbarItem {
                             Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
                         }),
                 )
-                .with_handle(self.model_selector_menu_handle.clone()),
+                .with_handle(self.language_model_selector_menu_handle.clone()),
             )
             .children(self.render_remaining_tokens(cx));
 

crates/assistant/src/inline_assistant.rs 🔗

@@ -33,7 +33,7 @@ use language_model::{
     LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
     LanguageModelTextStream, Role,
 };
-use language_model_selector::LanguageModelSelector;
+use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
 use language_models::report_assistant_event;
 use multi_buffer::MultiBufferRow;
 use parking_lot::Mutex;
@@ -1358,8 +1358,8 @@ enum PromptEditorEvent {
 
 struct PromptEditor {
     id: InlineAssistId,
-    fs: Arc<dyn Fs>,
     editor: View<Editor>,
+    language_model_selector: View<LanguageModelSelector>,
     edited_since_done: bool,
     gutter_dimensions: Arc<Mutex<GutterDimensions>>,
     prompt_history: VecDeque<String>,
@@ -1500,43 +1500,27 @@ impl Render for PromptEditor {
                     .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
                     .justify_center()
                     .gap_2()
-                    .child(
-                        LanguageModelSelector::new(
-                            {
-                                let fs = self.fs.clone();
-                                move |model, cx| {
-                                    update_settings_file::<AssistantSettings>(
-                                        fs.clone(),
-                                        cx,
-                                        move |settings, _| settings.set_model(model.clone()),
-                                    );
-                                }
-                            },
-                            IconButton::new("context", IconName::SettingsAlt)
-                                .shape(IconButtonShape::Square)
-                                .icon_size(IconSize::Small)
-                                .icon_color(Color::Muted)
-                                .tooltip(move |cx| {
-                                    Tooltip::with_meta(
-                                        format!(
-                                            "Using {}",
-                                            LanguageModelRegistry::read_global(cx)
-                                                .active_model()
-                                                .map(|model| model.name().0)
-                                                .unwrap_or_else(|| "No model selected".into()),
-                                        ),
-                                        None,
-                                        "Change Model",
-                                        cx,
-                                    )
-                                }),
-                        )
-                        .info_text(
-                            "Inline edits use context\n\
-                            from the currently selected\n\
-                            assistant panel tab.",
-                        ),
-                    )
+                    .child(LanguageModelSelectorPopoverMenu::new(
+                        self.language_model_selector.clone(),
+                        IconButton::new("context", IconName::SettingsAlt)
+                            .shape(IconButtonShape::Square)
+                            .icon_size(IconSize::Small)
+                            .icon_color(Color::Muted)
+                            .tooltip(move |cx| {
+                                Tooltip::with_meta(
+                                    format!(
+                                        "Using {}",
+                                        LanguageModelRegistry::read_global(cx)
+                                            .active_model()
+                                            .map(|model| model.name().0)
+                                            .unwrap_or_else(|| "No model selected".into()),
+                                    ),
+                                    None,
+                                    "Change Model",
+                                    cx,
+                                )
+                            }),
+                    ))
                     .map(|el| {
                         let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
                             return el;
@@ -1642,6 +1626,19 @@ impl PromptEditor {
         let mut this = Self {
             id,
             editor: prompt_editor,
+            language_model_selector: cx.new_view(|cx| {
+                let fs = fs.clone();
+                LanguageModelSelector::new(
+                    move |model, cx| {
+                        update_settings_file::<AssistantSettings>(
+                            fs.clone(),
+                            cx,
+                            move |settings, _| settings.set_model(model.clone()),
+                        );
+                    },
+                    cx,
+                )
+            }),
             edited_since_done: false,
             gutter_dimensions,
             prompt_history,
@@ -1650,7 +1647,6 @@ impl PromptEditor {
             _codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
             editor_subscriptions: Vec::new(),
             codegen,
-            fs,
             pending_token_count: Task::ready(Ok(())),
             token_counts: None,
             _token_count_subscriptions: token_count_subscriptions,

crates/assistant/src/terminal_inline_assistant.rs 🔗

@@ -20,7 +20,7 @@ use language::Buffer;
 use language_model::{
     LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
 };
-use language_model_selector::LanguageModelSelector;
+use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
 use language_models::report_assistant_event;
 use settings::{update_settings_file, Settings};
 use std::{
@@ -476,9 +476,9 @@ enum PromptEditorEvent {
 
 struct PromptEditor {
     id: TerminalInlineAssistId,
-    fs: Arc<dyn Fs>,
     height_in_lines: u8,
     editor: View<Editor>,
+    language_model_selector: View<LanguageModelSelector>,
     edited_since_done: bool,
     prompt_history: VecDeque<String>,
     prompt_history_ix: Option<usize>,
@@ -614,17 +614,8 @@ impl Render for PromptEditor {
                     .w_12()
                     .justify_center()
                     .gap_2()
-                    .child(LanguageModelSelector::new(
-                        {
-                            let fs = self.fs.clone();
-                            move |model, cx| {
-                                update_settings_file::<AssistantSettings>(
-                                    fs.clone(),
-                                    cx,
-                                    move |settings, _| settings.set_model(model.clone()),
-                                );
-                            }
-                        },
+                    .child(LanguageModelSelectorPopoverMenu::new(
+                        self.language_model_selector.clone(),
                         IconButton::new("context", IconName::SettingsAlt)
                             .shape(IconButtonShape::Square)
                             .icon_size(IconSize::Small)
@@ -718,6 +709,19 @@ impl PromptEditor {
             id,
             height_in_lines: 1,
             editor: prompt_editor,
+            language_model_selector: cx.new_view(|cx| {
+                let fs = fs.clone();
+                LanguageModelSelector::new(
+                    move |model, cx| {
+                        update_settings_file::<AssistantSettings>(
+                            fs.clone(),
+                            cx,
+                            move |settings, _| settings.set_model(model.clone()),
+                        );
+                    },
+                    cx,
+                )
+            }),
             edited_since_done: false,
             prompt_history,
             prompt_history_ix: None,
@@ -725,7 +729,6 @@ impl PromptEditor {
             _codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
             editor_subscriptions: Vec::new(),
             codegen,
-            fs,
             pending_token_count: Task::ready(Ok(())),
             token_count: None,
             _token_count_subscriptions: token_count_subscriptions,

crates/assistant2/src/inline_assistant.rs 🔗

@@ -31,7 +31,7 @@ use language_model::{
     LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
     LanguageModelTextStream, Role,
 };
-use language_model_selector::LanguageModelSelector;
+use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
 use language_models::report_assistant_event;
 use multi_buffer::MultiBufferRow;
 use parking_lot::Mutex;
@@ -1454,8 +1454,8 @@ enum PromptEditorEvent {
 
 struct PromptEditor {
     id: InlineAssistId,
-    fs: Arc<dyn Fs>,
     editor: View<Editor>,
+    language_model_selector: View<LanguageModelSelector>,
     edited_since_done: bool,
     gutter_dimensions: Arc<Mutex<GutterDimensions>>,
     prompt_history: VecDeque<String>,
@@ -1589,43 +1589,27 @@ impl Render for PromptEditor {
                     .w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
                     .justify_center()
                     .gap_2()
-                    .child(
-                        LanguageModelSelector::new(
-                            {
-                                let fs = self.fs.clone();
-                                move |model, cx| {
-                                    update_settings_file::<AssistantSettings>(
-                                        fs.clone(),
-                                        cx,
-                                        move |settings, _| settings.set_model(model.clone()),
-                                    );
-                                }
-                            },
-                            IconButton::new("context", IconName::SettingsAlt)
-                                .shape(IconButtonShape::Square)
-                                .icon_size(IconSize::Small)
-                                .icon_color(Color::Muted)
-                                .tooltip(move |cx| {
-                                    Tooltip::with_meta(
-                                        format!(
-                                            "Using {}",
-                                            LanguageModelRegistry::read_global(cx)
-                                                .active_model()
-                                                .map(|model| model.name().0)
-                                                .unwrap_or_else(|| "No model selected".into()),
-                                        ),
-                                        None,
-                                        "Change Model",
-                                        cx,
-                                    )
-                                }),
-                        )
-                        .info_text(
-                            "Inline edits use context\n\
-                            from the currently selected\n\
-                            assistant panel tab.",
-                        ),
-                    )
+                    .child(LanguageModelSelectorPopoverMenu::new(
+                        self.language_model_selector.clone(),
+                        IconButton::new("context", IconName::SettingsAlt)
+                            .shape(IconButtonShape::Square)
+                            .icon_size(IconSize::Small)
+                            .icon_color(Color::Muted)
+                            .tooltip(move |cx| {
+                                Tooltip::with_meta(
+                                    format!(
+                                        "Using {}",
+                                        LanguageModelRegistry::read_global(cx)
+                                            .active_model()
+                                            .map(|model| model.name().0)
+                                            .unwrap_or_else(|| "No model selected".into()),
+                                    ),
+                                    None,
+                                    "Change Model",
+                                    cx,
+                                )
+                            }),
+                    ))
                     .map(|el| {
                         let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
                             return el;
@@ -1714,6 +1698,19 @@ impl PromptEditor {
         let mut this = Self {
             id,
             editor: prompt_editor,
+            language_model_selector: cx.new_view(|cx| {
+                let fs = fs.clone();
+                LanguageModelSelector::new(
+                    move |model, cx| {
+                        update_settings_file::<AssistantSettings>(
+                            fs.clone(),
+                            cx,
+                            move |settings, _| settings.set_model(model.clone()),
+                        );
+                    },
+                    cx,
+                )
+            }),
             edited_since_done: false,
             gutter_dimensions,
             prompt_history,
@@ -1722,7 +1719,6 @@ impl PromptEditor {
             _codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
             editor_subscriptions: Vec::new(),
             codegen,
-            fs,
             show_rate_limit_notice: false,
         };
         this.subscribe_to_editor(cx);

crates/assistant2/src/message_editor.rs 🔗

@@ -3,7 +3,7 @@ use std::rc::Rc;
 use editor::{Editor, EditorElement, EditorStyle};
 use gpui::{AppContext, FocusableView, Model, TextStyle, View, WeakView};
 use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
-use language_model_selector::LanguageModelSelector;
+use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
 use settings::Settings;
 use theme::ThemeSettings;
 use ui::{
@@ -25,6 +25,7 @@ pub struct MessageEditor {
     next_context_id: ContextId,
     context_picker: View<ContextPicker>,
     pub(crate) context_picker_handle: PopoverMenuHandle<ContextPicker>,
+    language_model_selector: View<LanguageModelSelector>,
     use_tools: bool,
 }
 
@@ -47,6 +48,14 @@ impl MessageEditor {
             next_context_id: ContextId(0),
             context_picker: cx.new_view(|cx| ContextPicker::new(workspace.clone(), weak_self, cx)),
             context_picker_handle: PopoverMenuHandle::default(),
+            language_model_selector: cx.new_view(|cx| {
+                LanguageModelSelector::new(
+                    |model, _cx| {
+                        println!("Selected {:?}", model.name());
+                    },
+                    cx,
+                )
+            }),
             use_tools: false,
         }
     }
@@ -120,10 +129,8 @@ impl MessageEditor {
         let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
         let active_model = LanguageModelRegistry::read_global(cx).active_model();
 
-        LanguageModelSelector::new(
-            |model, _cx| {
-                println!("Selected {:?}", model.name());
-            },
+        LanguageModelSelectorPopoverMenu::new(
+            self.language_model_selector.clone(),
             ButtonLike::new("active-model")
                 .style(ButtonStyle::Subtle)
                 .child(

crates/assistant2/src/terminal_inline_assistant.rs 🔗

@@ -17,7 +17,7 @@ use language::Buffer;
 use language_model::{
     LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
 };
-use language_model_selector::LanguageModelSelector;
+use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
 use language_models::report_assistant_event;
 use settings::{update_settings_file, Settings};
 use std::{cmp, sync::Arc, time::Instant};
@@ -439,9 +439,9 @@ enum PromptEditorEvent {
 
 struct PromptEditor {
     id: TerminalInlineAssistId,
-    fs: Arc<dyn Fs>,
     height_in_lines: u8,
     editor: View<Editor>,
+    language_model_selector: View<LanguageModelSelector>,
     edited_since_done: bool,
     prompt_history: VecDeque<String>,
     prompt_history_ix: Option<usize>,
@@ -575,17 +575,8 @@ impl Render for PromptEditor {
                     .w_12()
                     .justify_center()
                     .gap_2()
-                    .child(LanguageModelSelector::new(
-                        {
-                            let fs = self.fs.clone();
-                            move |model, cx| {
-                                update_settings_file::<AssistantSettings>(
-                                    fs.clone(),
-                                    cx,
-                                    move |settings, _| settings.set_model(model.clone()),
-                                );
-                            }
-                        },
+                    .child(LanguageModelSelectorPopoverMenu::new(
+                        self.language_model_selector.clone(),
                         IconButton::new("context", IconName::SettingsAlt)
                             .shape(IconButtonShape::Square)
                             .icon_size(IconSize::Small)
@@ -665,6 +656,19 @@ impl PromptEditor {
             id,
             height_in_lines: 1,
             editor: prompt_editor,
+            language_model_selector: cx.new_view(|cx| {
+                let fs = fs.clone();
+                LanguageModelSelector::new(
+                    move |model, cx| {
+                        update_settings_file::<AssistantSettings>(
+                            fs.clone(),
+                            cx,
+                            move |settings, _| settings.set_model(model.clone()),
+                        );
+                    },
+                    cx,
+                )
+            }),
             edited_since_done: false,
             prompt_history,
             prompt_history_ix: None,
@@ -672,7 +676,6 @@ impl PromptEditor {
             _codegen_subscription: cx.observe(&codegen, Self::handle_codegen_changed),
             editor_subscriptions: Vec::new(),
             codegen,
-            fs,
         };
         this.count_lines(cx);
         this.subscribe_to_editor(cx);

crates/language_model_selector/src/language_model_selector.rs 🔗

@@ -1,7 +1,10 @@
 use std::sync::Arc;
 
 use feature_flags::ZedPro;
-use gpui::{Action, AnyElement, AppContext, DismissEvent, SharedString, Task};
+use gpui::{
+    Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Task,
+    View, WeakView,
+};
 use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
 use picker::{Picker, PickerDelegate};
 use proto::Plan;
@@ -12,56 +15,118 @@ const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
 
 type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &AppContext) + 'static>;
 
-#[derive(IntoElement)]
-pub struct LanguageModelSelector<T: PopoverTrigger> {
-    handle: Option<PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>>,
-    on_model_changed: OnModelChanged,
-    trigger: T,
-    info_text: Option<SharedString>,
+pub struct LanguageModelSelector {
+    picker: View<Picker<LanguageModelPickerDelegate>>,
 }
 
-pub struct LanguageModelPickerDelegate {
-    on_model_changed: OnModelChanged,
-    all_models: Vec<ModelInfo>,
-    filtered_models: Vec<ModelInfo>,
-    selected_index: usize,
+impl LanguageModelSelector {
+    pub fn new(
+        on_model_changed: impl Fn(Arc<dyn LanguageModel>, &AppContext) + 'static,
+        cx: &mut ViewContext<Self>,
+    ) -> Self {
+        let on_model_changed = Arc::new(on_model_changed);
+
+        let all_models = LanguageModelRegistry::global(cx)
+            .read(cx)
+            .providers()
+            .iter()
+            .flat_map(|provider| {
+                let icon = provider.icon();
+
+                provider.provided_models(cx).into_iter().map(move |model| {
+                    let model = model.clone();
+                    let icon = model.icon().unwrap_or(icon);
+
+                    ModelInfo {
+                        model: model.clone(),
+                        icon,
+                        availability: model.availability(),
+                    }
+                })
+            })
+            .collect::<Vec<_>>();
+
+        let delegate = LanguageModelPickerDelegate {
+            language_model_selector: cx.view().downgrade(),
+            on_model_changed: on_model_changed.clone(),
+            all_models: all_models.clone(),
+            filtered_models: all_models,
+            selected_index: 0,
+        };
+
+        let picker =
+            cx.new_view(|cx| Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())));
+
+        LanguageModelSelector { picker }
+    }
 }
 
-#[derive(Clone)]
-struct ModelInfo {
-    model: Arc<dyn LanguageModel>,
-    icon: IconName,
-    availability: LanguageModelAvailability,
-    is_selected: bool,
+impl EventEmitter<DismissEvent> for LanguageModelSelector {}
+
+impl FocusableView for LanguageModelSelector {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
 }
 
-impl<T: PopoverTrigger> LanguageModelSelector<T> {
-    pub fn new(
-        on_model_changed: impl Fn(Arc<dyn LanguageModel>, &AppContext) + 'static,
-        trigger: T,
-    ) -> Self {
-        LanguageModelSelector {
-            handle: None,
-            on_model_changed: Arc::new(on_model_changed),
+impl Render for LanguageModelSelector {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        self.picker.clone()
+    }
+}
+
+#[derive(IntoElement)]
+pub struct LanguageModelSelectorPopoverMenu<T>
+where
+    T: PopoverTrigger,
+{
+    language_model_selector: View<LanguageModelSelector>,
+    trigger: T,
+    handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
+}
+
+impl<T: PopoverTrigger> LanguageModelSelectorPopoverMenu<T> {
+    pub fn new(language_model_selector: View<LanguageModelSelector>, trigger: T) -> Self {
+        Self {
+            language_model_selector,
             trigger,
-            info_text: None,
+            handle: None,
         }
     }
 
-    pub fn with_handle(
-        mut self,
-        handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
-    ) -> Self {
+    pub fn with_handle(mut self, handle: PopoverMenuHandle<LanguageModelSelector>) -> Self {
         self.handle = Some(handle);
         self
     }
+}
 
-    pub fn info_text(mut self, text: impl Into<SharedString>) -> Self {
-        self.info_text = Some(text.into());
-        self
+impl<T: PopoverTrigger> RenderOnce for LanguageModelSelectorPopoverMenu<T> {
+    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+        let language_model_selector = self.language_model_selector.clone();
+
+        PopoverMenu::new("model-switcher")
+            .menu(move |_cx| Some(language_model_selector.clone()))
+            .trigger(self.trigger)
+            .attach(gpui::AnchorCorner::BottomLeft)
+            .when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
     }
 }
 
+#[derive(Clone)]
+struct ModelInfo {
+    model: Arc<dyn LanguageModel>,
+    icon: IconName,
+    availability: LanguageModelAvailability,
+}
+
+pub struct LanguageModelPickerDelegate {
+    language_model_selector: WeakView<LanguageModelSelector>,
+    on_model_changed: OnModelChanged,
+    all_models: Vec<ModelInfo>,
+    filtered_models: Vec<ModelInfo>,
+    selected_index: usize,
+}
+
 impl PickerDelegate for LanguageModelPickerDelegate {
     type ListItem = ListItem;
 
@@ -142,23 +207,15 @@ impl PickerDelegate for LanguageModelPickerDelegate {
             let model = model_info.model.clone();
             (self.on_model_changed)(model.clone(), cx);
 
-            // Update the selection status
-            let selected_model_id = model_info.model.id();
-            let selected_provider_id = model_info.model.provider_id();
-            for model in &mut self.all_models {
-                model.is_selected = model.model.id() == selected_model_id
-                    && model.model.provider_id() == selected_provider_id;
-            }
-            for model in &mut self.filtered_models {
-                model.is_selected = model.model.id() == selected_model_id
-                    && model.model.provider_id() == selected_provider_id;
-            }
-
             cx.emit(DismissEvent);
         }
     }
 
-    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+        self.language_model_selector
+            .update(cx, |_this, cx| cx.emit(DismissEvent))
+            .ok();
+    }
 
     fn render_header(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
         let configured_models_count = LanguageModelRegistry::global(cx)
@@ -195,6 +252,17 @@ impl PickerDelegate for LanguageModelPickerDelegate {
         let model_info = self.filtered_models.get(ix)?;
         let provider_name: String = model_info.model.provider_name().0.clone().into();
 
+        let active_provider_id = LanguageModelRegistry::read_global(cx)
+            .active_provider()
+            .map(|m| m.id());
+
+        let active_model_id = LanguageModelRegistry::read_global(cx)
+            .active_model()
+            .map(|m| m.id());
+
+        let is_selected = Some(model_info.model.provider_id()) == active_provider_id
+            && Some(model_info.model.id()) == active_model_id;
+
         Some(
             ListItem::new(ix)
                 .inset(true)
@@ -235,7 +303,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
                                 }),
                         ),
                 )
-                .end_slot(div().when(model_info.is_selected, |this| {
+                .end_slot(div().when(is_selected, |this| {
                     this.child(
                         Icon::new(IconName::Check)
                             .color(Color::Accent)
@@ -296,58 +364,3 @@ impl PickerDelegate for LanguageModelPickerDelegate {
         )
     }
 }
-
-impl<T: PopoverTrigger> RenderOnce for LanguageModelSelector<T> {
-    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
-        let selected_provider = LanguageModelRegistry::read_global(cx)
-            .active_provider()
-            .map(|m| m.id());
-
-        let selected_model = LanguageModelRegistry::read_global(cx)
-            .active_model()
-            .map(|m| m.id());
-
-        let all_models = LanguageModelRegistry::global(cx)
-            .read(cx)
-            .providers()
-            .iter()
-            .flat_map(|provider| {
-                let provider_id = provider.id();
-                let icon = provider.icon();
-                let selected_model = selected_model.clone();
-                let selected_provider = selected_provider.clone();
-
-                provider.provided_models(cx).into_iter().map(move |model| {
-                    let model = model.clone();
-                    let icon = model.icon().unwrap_or(icon);
-
-                    ModelInfo {
-                        model: model.clone(),
-                        icon,
-                        availability: model.availability(),
-                        is_selected: selected_model.as_ref() == Some(&model.id())
-                            && selected_provider.as_ref() == Some(&provider_id),
-                    }
-                })
-            })
-            .collect::<Vec<_>>();
-
-        let delegate = LanguageModelPickerDelegate {
-            on_model_changed: self.on_model_changed.clone(),
-            all_models: all_models.clone(),
-            filtered_models: all_models,
-            selected_index: 0,
-        };
-
-        let picker_view = cx.new_view(|cx| {
-            let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into()));
-            picker
-        });
-
-        PopoverMenu::new("model-switcher")
-            .menu(move |_cx| Some(picker_view.clone()))
-            .trigger(self.trigger)
-            .attach(gpui::AnchorCorner::BottomLeft)
-            .when_some(self.handle, |menu, handle| menu.with_handle(handle))
-    }
-}