From 3a0408953d25f3cbb16902a021e1c699d6883b18 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 23 Nov 2024 12:11:31 -0500 Subject: [PATCH] Factor out language model selector into its own crate (#21113) This PR factors the language model selector out into its own `language_model_selector` crate so that it can be reused in `assistant2`. Also renamed it from `ModelSelector` to `LanguageModelSelector` to be a bit more specific. Release Notes: - N/A --- Cargo.lock | 15 +++++ Cargo.toml | 2 + crates/assistant/Cargo.toml | 1 + crates/assistant/src/assistant.rs | 2 - crates/assistant/src/assistant_panel.rs | 26 ++++++--- crates/assistant/src/inline_assistant.rs | 20 +++++-- .../src/terminal_inline_assistant.rs | 21 +++++-- crates/language_model_selector/Cargo.toml | 22 +++++++ crates/language_model_selector/LICENSE-GPL | 1 + .../src/language_model_selector.rs} | 57 ++++++++++--------- 10 files changed, 119 insertions(+), 48 deletions(-) create mode 100644 crates/language_model_selector/Cargo.toml create mode 120000 crates/language_model_selector/LICENSE-GPL rename crates/{assistant/src/model_selector.rs => language_model_selector/src/language_model_selector.rs} (90%) diff --git a/Cargo.lock b/Cargo.lock index 8138744707..cb2e662311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,6 +402,7 @@ dependencies = [ "indoc", "language", "language_model", + "language_model_selector", "language_models", "languages", "log", @@ -6603,6 +6604,20 @@ dependencies = [ "util", ] +[[package]] +name = "language_model_selector" +version = "0.1.0" +dependencies = [ + "feature_flags", + "gpui", + "language_model", + "picker", + "proto", + "ui", + "workspace", + "zed_actions", +] + [[package]] name = "language_models" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a8537611fe..2e5111e2ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "crates/language", "crates/language_extension", "crates/language_model", + "crates/language_model_selector", "crates/language_models", "crates/language_selector", "crates/language_tools", @@ -236,6 +237,7 @@ journal = { path = "crates/journal" } language = { path = "crates/language" } language_extension = { path = "crates/language_extension" } language_model = { path = "crates/language_model" } +language_model_selector = { path = "crates/language_model_selector" } language_models = { path = "crates/language_models" } language_selector = { path = "crates/language_selector" } language_tools = { path = "crates/language_tools" } diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 7f5aef3f46..0799d4bbdb 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -50,6 +50,7 @@ indexed_docs.workspace = true indoc.workspace = true language.workspace = true language_model.workspace = true +language_model_selector.workspace = true language_models.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index b891c3da2a..f6e435bfb8 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -5,7 +5,6 @@ pub mod assistant_settings; mod context; pub mod context_store; mod inline_assistant; -mod model_selector; mod patch; mod prompt_library; mod prompts; @@ -37,7 +36,6 @@ pub(crate) use inline_assistant::*; use language_model::{ LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, }; -pub(crate) use model_selector::*; pub use patch::*; pub use prompts::PromptBuilder; use prompts::PromptLoadingParams; diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index ff60f2b918..9a7beb96d2 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -17,9 +17,9 @@ use crate::{ ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles, InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, - MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, - ParsedSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, - RequestType, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, + MessageMetadata, MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus, + QuoteSelection, RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus, + ToggleModelSelector, }; use anyhow::Result; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection}; @@ -55,6 +55,7 @@ use language_model::{ LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role, ZED_CLOUD_PROVIDER_ID, }; +use language_model_selector::{LanguageModelPickerDelegate, LanguageModelSelector}; use multi_buffer::MultiBufferRow; use picker::{Picker, PickerDelegate}; use project::lsp_store::LocalLspAdapterDelegate; @@ -142,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, @@ -4457,13 +4458,13 @@ pub struct ContextEditorToolbarItem { fs: Arc, active_context_editor: Option>, model_summary_editor: View, - model_selector_menu_handle: PopoverMenuHandle>, + 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, ) -> Self { Self { @@ -4559,8 +4560,17 @@ impl Render for ContextEditorToolbarItem { // .map(|remaining_items| format!("Files to scan: {}", remaining_items)) // }) .child( - ModelSelector::new( - self.fs.clone(), + LanguageModelSelector::new( + { + let fs = self.fs.clone(); + move |model, cx| { + update_settings_file::( + fs.clone(), + cx, + move |settings, _| settings.set_model(model.clone()), + ); + } + }, ButtonLike::new("active-model") .style(ButtonStyle::Subtle) .child( diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 855972c267..b1cb1d81b4 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1,7 +1,7 @@ use crate::{ assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist, - CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, RequestType, StreamingDiff, + CyclePreviousInlineAssist, LineDiff, LineOperation, RequestType, StreamingDiff, }; use anyhow::{anyhow, Context as _, Result}; use client::{telemetry::Telemetry, ErrorExt}; @@ -33,12 +33,13 @@ use language_model::{ LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModelTextStream, Role, }; +use language_model_selector::LanguageModelSelector; use language_models::report_assistant_event; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; use project::{CodeAction, ProjectTransaction}; use rope::Rope; -use settings::{Settings, SettingsStore}; +use settings::{update_settings_file, Settings, SettingsStore}; use smol::future::FutureExt; use std::{ cmp, @@ -1500,8 +1501,17 @@ impl Render for PromptEditor { .justify_center() .gap_2() .child( - ModelSelector::new( - self.fs.clone(), + 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) @@ -1521,7 +1531,7 @@ impl Render for PromptEditor { ) }), ) - .with_info_text( + .info_text( "Inline edits use context\n\ from the currently selected\n\ assistant panel tab.", diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index 51738b90e4..a5424a8d7e 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/crates/assistant/src/terminal_inline_assistant.rs @@ -1,6 +1,7 @@ +use crate::assistant_settings::AssistantSettings; use crate::{ - humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, - ModelSelector, RequestType, DEFAULT_CONTEXT_LINES, + humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, RequestType, + DEFAULT_CONTEXT_LINES, }; use anyhow::{Context as _, Result}; use client::telemetry::Telemetry; @@ -19,8 +20,9 @@ use language::Buffer; use language_model::{ LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, }; +use language_model_selector::LanguageModelSelector; use language_models::report_assistant_event; -use settings::Settings; +use settings::{update_settings_file, Settings}; use std::{ cmp, sync::Arc, @@ -612,8 +614,17 @@ impl Render for PromptEditor { .w_12() .justify_center() .gap_2() - .child(ModelSelector::new( - self.fs.clone(), + .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) diff --git a/crates/language_model_selector/Cargo.toml b/crates/language_model_selector/Cargo.toml new file mode 100644 index 0000000000..cd00af50c0 --- /dev/null +++ b/crates/language_model_selector/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "language_model_selector" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/language_model_selector.rs" + +[dependencies] +feature_flags.workspace = true +gpui.workspace = true +language_model.workspace = true +picker.workspace = true +proto.workspace = true +ui.workspace = true +workspace.workspace = true +zed_actions.workspace = true diff --git a/crates/language_model_selector/LICENSE-GPL b/crates/language_model_selector/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/language_model_selector/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/assistant/src/model_selector.rs b/crates/language_model_selector/src/language_model_selector.rs similarity index 90% rename from crates/assistant/src/model_selector.rs rename to crates/language_model_selector/src/language_model_selector.rs index 1b26b8b5ad..562bccbd88 100644 --- a/crates/assistant/src/model_selector.rs +++ b/crates/language_model_selector/src/language_model_selector.rs @@ -1,30 +1,27 @@ -use feature_flags::ZedPro; - -use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry}; -use proto::Plan; -use workspace::ShowConfiguration; - use std::sync::Arc; -use crate::assistant_settings::AssistantSettings; -use fs::Fs; -use gpui::{Action, AnyElement, DismissEvent, SharedString, Task}; +use feature_flags::ZedPro; +use gpui::{Action, AnyElement, AppContext, DismissEvent, SharedString, Task}; +use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry}; use picker::{Picker, PickerDelegate}; -use settings::update_settings_file; +use proto::Plan; use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger}; +use workspace::ShowConfiguration; const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro"; +type OnModelChanged = Arc, &AppContext) + 'static>; + #[derive(IntoElement)] -pub struct ModelSelector { - handle: Option>>, - fs: Arc, +pub struct LanguageModelSelector { + handle: Option>>, + on_model_changed: OnModelChanged, trigger: T, info_text: Option, } -pub struct ModelPickerDelegate { - fs: Arc, +pub struct LanguageModelPickerDelegate { + on_model_changed: OnModelChanged, all_models: Vec, filtered_models: Vec, selected_index: usize, @@ -38,28 +35,34 @@ struct ModelInfo { is_selected: bool, } -impl ModelSelector { - pub fn new(fs: Arc, trigger: T) -> Self { - ModelSelector { +impl LanguageModelSelector { + pub fn new( + on_model_changed: impl Fn(Arc, &AppContext) + 'static, + trigger: T, + ) -> Self { + LanguageModelSelector { handle: None, - fs, + on_model_changed: Arc::new(on_model_changed), trigger, info_text: None, } } - pub fn with_handle(mut self, handle: PopoverMenuHandle>) -> Self { + pub fn with_handle( + mut self, + handle: PopoverMenuHandle>, + ) -> Self { self.handle = Some(handle); self } - pub fn with_info_text(mut self, text: impl Into) -> Self { + pub fn info_text(mut self, text: impl Into) -> Self { self.info_text = Some(text.into()); self } } -impl PickerDelegate for ModelPickerDelegate { +impl PickerDelegate for LanguageModelPickerDelegate { type ListItem = ListItem; fn match_count(&self) -> usize { @@ -137,9 +140,7 @@ impl PickerDelegate for ModelPickerDelegate { fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext>) { if let Some(model_info) = self.filtered_models.get(self.selected_index) { let model = model_info.model.clone(); - update_settings_file::(self.fs.clone(), cx, move |settings, _| { - settings.set_model(model.clone()) - }); + (self.on_model_changed)(model.clone(), cx); // Update the selection status let selected_model_id = model_info.model.id(); @@ -296,7 +297,7 @@ impl PickerDelegate for ModelPickerDelegate { } } -impl RenderOnce for ModelSelector { +impl RenderOnce for LanguageModelSelector { fn render(self, cx: &mut WindowContext) -> impl IntoElement { let selected_provider = LanguageModelRegistry::read_global(cx) .active_provider() @@ -331,8 +332,8 @@ impl RenderOnce for ModelSelector { }) .collect::>(); - let delegate = ModelPickerDelegate { - fs: self.fs.clone(), + let delegate = LanguageModelPickerDelegate { + on_model_changed: self.on_model_changed.clone(), all_models: all_models.clone(), filtered_models: all_models, selected_index: 0,