From 9143fd29247913c9b297410739714c51fecf44b8 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 12 Dec 2024 17:08:48 -0500 Subject: [PATCH] language_model_selector: Don't recreate the `Picker` view each render (#21939) 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 --- crates/assistant/src/assistant_panel.rs | 45 ++-- crates/assistant/src/inline_assistant.rs | 76 +++--- .../src/terminal_inline_assistant.rs | 31 +-- crates/assistant2/src/inline_assistant.rs | 76 +++--- crates/assistant2/src/message_editor.rs | 17 +- .../src/terminal_inline_assistant.rs | 31 +-- .../src/language_model_selector.rs | 227 +++++++++--------- 7 files changed, 263 insertions(+), 240 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 21112baa1f..285676e222 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/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, fs: Arc, subscriptions: Vec, - model_selector_menu_handle: PopoverMenuHandle>, + model_selector_menu_handle: PopoverMenuHandle, model_summary_editor: View, authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>, configuration_subscription: Option, @@ -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, active_context_editor: Option>, model_summary_editor: View, - model_selector_menu_handle: PopoverMenuHandle>, + language_model_selector: View, + language_model_selector_menu_handle: PopoverMenuHandle, } impl ContextEditorToolbarItem { pub fn new( workspace: &Workspace, - model_selector_menu_handle: PopoverMenuHandle>, + model_selector_menu_handle: PopoverMenuHandle, model_summary_editor: View, + cx: &mut ViewContext, ) -> 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::( + 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::( - 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)); diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index b1cb1d81b4..bfbdd21677 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/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, editor: View, + language_model_selector: View, edited_since_done: bool, gutter_dimensions: Arc>, prompt_history: VecDeque, @@ -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::( - 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::( + 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, diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index d60a556cf0..719e69256d 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/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, height_in_lines: u8, editor: View, + language_model_selector: View, edited_since_done: bool, prompt_history: VecDeque, prompt_history_ix: Option, @@ -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::( - 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::( + 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, diff --git a/crates/assistant2/src/inline_assistant.rs b/crates/assistant2/src/inline_assistant.rs index d007c09031..1cc5fda5d3 100644 --- a/crates/assistant2/src/inline_assistant.rs +++ b/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, editor: View, + language_model_selector: View, edited_since_done: bool, gutter_dimensions: Arc>, prompt_history: VecDeque, @@ -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::( - 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::( + 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); diff --git a/crates/assistant2/src/message_editor.rs b/crates/assistant2/src/message_editor.rs index aa4b7a7836..2bb9b78335 100644 --- a/crates/assistant2/src/message_editor.rs +++ b/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, pub(crate) context_picker_handle: PopoverMenuHandle, + language_model_selector: View, 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( diff --git a/crates/assistant2/src/terminal_inline_assistant.rs b/crates/assistant2/src/terminal_inline_assistant.rs index 146edfe68b..1c5574e1d9 100644 --- a/crates/assistant2/src/terminal_inline_assistant.rs +++ b/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, height_in_lines: u8, editor: View, + language_model_selector: View, edited_since_done: bool, prompt_history: VecDeque, prompt_history_ix: Option, @@ -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::( - 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::( + 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); diff --git a/crates/language_model_selector/src/language_model_selector.rs b/crates/language_model_selector/src/language_model_selector.rs index 562bccbd88..b76f88e557 100644 --- a/crates/language_model_selector/src/language_model_selector.rs +++ b/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,19 +15,101 @@ const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro"; type OnModelChanged = Arc, &AppContext) + 'static>; -#[derive(IntoElement)] -pub struct LanguageModelSelector { - handle: Option>>, - on_model_changed: OnModelChanged, - trigger: T, - info_text: Option, +pub struct LanguageModelSelector { + picker: View>, } -pub struct LanguageModelPickerDelegate { - on_model_changed: OnModelChanged, - all_models: Vec, - filtered_models: Vec, - selected_index: usize, +impl LanguageModelSelector { + pub fn new( + on_model_changed: impl Fn(Arc, &AppContext) + 'static, + cx: &mut ViewContext, + ) -> 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::>(); + + 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 } + } +} + +impl EventEmitter for LanguageModelSelector {} + +impl FocusableView for LanguageModelSelector { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.picker.focus_handle(cx) + } +} + +impl Render for LanguageModelSelector { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + self.picker.clone() + } +} + +#[derive(IntoElement)] +pub struct LanguageModelSelectorPopoverMenu +where + T: PopoverTrigger, +{ + language_model_selector: View, + trigger: T, + handle: Option>, +} + +impl LanguageModelSelectorPopoverMenu { + pub fn new(language_model_selector: View, trigger: T) -> Self { + Self { + language_model_selector, + trigger, + handle: None, + } + } + + pub fn with_handle(mut self, handle: PopoverMenuHandle) -> Self { + self.handle = Some(handle); + self + } +} + +impl RenderOnce for LanguageModelSelectorPopoverMenu { + 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)] @@ -32,34 +117,14 @@ struct ModelInfo { model: Arc, icon: IconName, availability: LanguageModelAvailability, - is_selected: bool, } -impl LanguageModelSelector { - pub fn new( - on_model_changed: impl Fn(Arc, &AppContext) + 'static, - trigger: T, - ) -> Self { - LanguageModelSelector { - handle: None, - on_model_changed: Arc::new(on_model_changed), - trigger, - info_text: None, - } - } - - pub fn with_handle( - mut self, - handle: PopoverMenuHandle>, - ) -> Self { - self.handle = Some(handle); - self - } - - pub fn info_text(mut self, text: impl Into) -> Self { - self.info_text = Some(text.into()); - self - } +pub struct LanguageModelPickerDelegate { + language_model_selector: WeakView, + on_model_changed: OnModelChanged, + all_models: Vec, + filtered_models: Vec, + selected_index: usize, } impl PickerDelegate for LanguageModelPickerDelegate { @@ -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>) {} + fn dismissed(&mut self, cx: &mut ViewContext>) { + self.language_model_selector + .update(cx, |_this, cx| cx.emit(DismissEvent)) + .ok(); + } fn render_header(&self, cx: &mut ViewContext>) -> Option { 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 RenderOnce for LanguageModelSelector { - 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::>(); - - 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)) - } -}