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
This commit is contained in:
Marshall Bowers 2024-11-23 12:11:31 -05:00 committed by GitHub
parent 9adc3b4e82
commit 3a0408953d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 119 additions and 48 deletions

15
Cargo.lock generated
View file

@ -402,6 +402,7 @@ dependencies = [
"indoc", "indoc",
"language", "language",
"language_model", "language_model",
"language_model_selector",
"language_models", "language_models",
"languages", "languages",
"log", "log",
@ -6603,6 +6604,20 @@ dependencies = [
"util", "util",
] ]
[[package]]
name = "language_model_selector"
version = "0.1.0"
dependencies = [
"feature_flags",
"gpui",
"language_model",
"picker",
"proto",
"ui",
"workspace",
"zed_actions",
]
[[package]] [[package]]
name = "language_models" name = "language_models"
version = "0.1.0" version = "0.1.0"

View file

@ -58,6 +58,7 @@ members = [
"crates/language", "crates/language",
"crates/language_extension", "crates/language_extension",
"crates/language_model", "crates/language_model",
"crates/language_model_selector",
"crates/language_models", "crates/language_models",
"crates/language_selector", "crates/language_selector",
"crates/language_tools", "crates/language_tools",
@ -236,6 +237,7 @@ journal = { path = "crates/journal" }
language = { path = "crates/language" } language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" } language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" } language_model = { path = "crates/language_model" }
language_model_selector = { path = "crates/language_model_selector" }
language_models = { path = "crates/language_models" } language_models = { path = "crates/language_models" }
language_selector = { path = "crates/language_selector" } language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" } language_tools = { path = "crates/language_tools" }

View file

@ -50,6 +50,7 @@ indexed_docs.workspace = true
indoc.workspace = true indoc.workspace = true
language.workspace = true language.workspace = true
language_model.workspace = true language_model.workspace = true
language_model_selector.workspace = true
language_models.workspace = true language_models.workspace = true
log.workspace = true log.workspace = true
lsp.workspace = true lsp.workspace = true

View file

@ -5,7 +5,6 @@ pub mod assistant_settings;
mod context; mod context;
pub mod context_store; pub mod context_store;
mod inline_assistant; mod inline_assistant;
mod model_selector;
mod patch; mod patch;
mod prompt_library; mod prompt_library;
mod prompts; mod prompts;
@ -37,7 +36,6 @@ pub(crate) use inline_assistant::*;
use language_model::{ use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
}; };
pub(crate) use model_selector::*;
pub use patch::*; pub use patch::*;
pub use prompts::PromptBuilder; pub use prompts::PromptBuilder;
use prompts::PromptLoadingParams; use prompts::PromptLoadingParams;

View file

@ -17,9 +17,9 @@ use crate::{
ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole, ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole,
DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles, DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles,
InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, MessageMetadata, MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus,
ParsedSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, QuoteSelection, RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus,
RequestType, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, ToggleModelSelector,
}; };
use anyhow::Result; use anyhow::Result;
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection}; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
@ -55,6 +55,7 @@ use language_model::{
LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role, LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role,
ZED_CLOUD_PROVIDER_ID, ZED_CLOUD_PROVIDER_ID,
}; };
use language_model_selector::{LanguageModelPickerDelegate, LanguageModelSelector};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::lsp_store::LocalLspAdapterDelegate; use project::lsp_store::LocalLspAdapterDelegate;
@ -142,7 +143,7 @@ pub struct AssistantPanel {
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
subscriptions: Vec<Subscription>, subscriptions: Vec<Subscription>,
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>, model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
model_summary_editor: View<Editor>, model_summary_editor: View<Editor>,
authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>, authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
configuration_subscription: Option<Subscription>, configuration_subscription: Option<Subscription>,
@ -4457,13 +4458,13 @@ pub struct ContextEditorToolbarItem {
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
active_context_editor: Option<WeakView<ContextEditor>>, active_context_editor: Option<WeakView<ContextEditor>>,
model_summary_editor: View<Editor>, model_summary_editor: View<Editor>,
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>, model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
} }
impl ContextEditorToolbarItem { impl ContextEditorToolbarItem {
pub fn new( pub fn new(
workspace: &Workspace, workspace: &Workspace,
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>, model_selector_menu_handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
model_summary_editor: View<Editor>, model_summary_editor: View<Editor>,
) -> Self { ) -> Self {
Self { Self {
@ -4559,8 +4560,17 @@ impl Render for ContextEditorToolbarItem {
// .map(|remaining_items| format!("Files to scan: {}", remaining_items)) // .map(|remaining_items| format!("Files to scan: {}", remaining_items))
// }) // })
.child( .child(
ModelSelector::new( LanguageModelSelector::new(
self.fs.clone(), {
let fs = self.fs.clone();
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
}
},
ButtonLike::new("active-model") ButtonLike::new("active-model")
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.child( .child(

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder, assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder,
AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist, AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist,
CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, RequestType, StreamingDiff, CyclePreviousInlineAssist, LineDiff, LineOperation, RequestType, StreamingDiff,
}; };
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use client::{telemetry::Telemetry, ErrorExt}; use client::{telemetry::Telemetry, ErrorExt};
@ -33,12 +33,13 @@ use language_model::{
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
LanguageModelTextStream, Role, LanguageModelTextStream, Role,
}; };
use language_model_selector::LanguageModelSelector;
use language_models::report_assistant_event; use language_models::report_assistant_event;
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{CodeAction, ProjectTransaction}; use project::{CodeAction, ProjectTransaction};
use rope::Rope; use rope::Rope;
use settings::{Settings, SettingsStore}; use settings::{update_settings_file, Settings, SettingsStore};
use smol::future::FutureExt; use smol::future::FutureExt;
use std::{ use std::{
cmp, cmp,
@ -1500,8 +1501,17 @@ impl Render for PromptEditor {
.justify_center() .justify_center()
.gap_2() .gap_2()
.child( .child(
ModelSelector::new( LanguageModelSelector::new(
self.fs.clone(), {
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) IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -1521,7 +1531,7 @@ impl Render for PromptEditor {
) )
}), }),
) )
.with_info_text( .info_text(
"Inline edits use context\n\ "Inline edits use context\n\
from the currently selected\n\ from the currently selected\n\
assistant panel tab.", assistant panel tab.",

View file

@ -1,6 +1,7 @@
use crate::assistant_settings::AssistantSettings;
use crate::{ use crate::{
humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, RequestType,
ModelSelector, RequestType, DEFAULT_CONTEXT_LINES, DEFAULT_CONTEXT_LINES,
}; };
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use client::telemetry::Telemetry; use client::telemetry::Telemetry;
@ -19,8 +20,9 @@ use language::Buffer;
use language_model::{ use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
}; };
use language_model_selector::LanguageModelSelector;
use language_models::report_assistant_event; use language_models::report_assistant_event;
use settings::Settings; use settings::{update_settings_file, Settings};
use std::{ use std::{
cmp, cmp,
sync::Arc, sync::Arc,
@ -612,8 +614,17 @@ impl Render for PromptEditor {
.w_12() .w_12()
.justify_center() .justify_center()
.gap_2() .gap_2()
.child(ModelSelector::new( .child(LanguageModelSelector::new(
self.fs.clone(), {
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) IconButton::new("context", IconName::SettingsAlt)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -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

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -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 std::sync::Arc;
use crate::assistant_settings::AssistantSettings; use feature_flags::ZedPro;
use fs::Fs; use gpui::{Action, AnyElement, AppContext, DismissEvent, SharedString, Task};
use gpui::{Action, AnyElement, DismissEvent, SharedString, Task}; use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry};
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use settings::update_settings_file; use proto::Plan;
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger}; use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
use workspace::ShowConfiguration;
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro"; const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &AppContext) + 'static>;
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct ModelSelector<T: PopoverTrigger> { pub struct LanguageModelSelector<T: PopoverTrigger> {
handle: Option<PopoverMenuHandle<Picker<ModelPickerDelegate>>>, handle: Option<PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>>,
fs: Arc<dyn Fs>, on_model_changed: OnModelChanged,
trigger: T, trigger: T,
info_text: Option<SharedString>, info_text: Option<SharedString>,
} }
pub struct ModelPickerDelegate { pub struct LanguageModelPickerDelegate {
fs: Arc<dyn Fs>, on_model_changed: OnModelChanged,
all_models: Vec<ModelInfo>, all_models: Vec<ModelInfo>,
filtered_models: Vec<ModelInfo>, filtered_models: Vec<ModelInfo>,
selected_index: usize, selected_index: usize,
@ -38,28 +35,34 @@ struct ModelInfo {
is_selected: bool, is_selected: bool,
} }
impl<T: PopoverTrigger> ModelSelector<T> { impl<T: PopoverTrigger> LanguageModelSelector<T> {
pub fn new(fs: Arc<dyn Fs>, trigger: T) -> Self { pub fn new(
ModelSelector { on_model_changed: impl Fn(Arc<dyn LanguageModel>, &AppContext) + 'static,
trigger: T,
) -> Self {
LanguageModelSelector {
handle: None, handle: None,
fs, on_model_changed: Arc::new(on_model_changed),
trigger, trigger,
info_text: None, info_text: None,
} }
} }
pub fn with_handle(mut self, handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>) -> Self { pub fn with_handle(
mut self,
handle: PopoverMenuHandle<Picker<LanguageModelPickerDelegate>>,
) -> Self {
self.handle = Some(handle); self.handle = Some(handle);
self self
} }
pub fn with_info_text(mut self, text: impl Into<SharedString>) -> Self { pub fn info_text(mut self, text: impl Into<SharedString>) -> Self {
self.info_text = Some(text.into()); self.info_text = Some(text.into());
self self
} }
} }
impl PickerDelegate for ModelPickerDelegate { impl PickerDelegate for LanguageModelPickerDelegate {
type ListItem = ListItem; type ListItem = ListItem;
fn match_count(&self) -> usize { fn match_count(&self) -> usize {
@ -137,9 +140,7 @@ impl PickerDelegate for ModelPickerDelegate {
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) { fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(model_info) = self.filtered_models.get(self.selected_index) { if let Some(model_info) = self.filtered_models.get(self.selected_index) {
let model = model_info.model.clone(); let model = model_info.model.clone();
update_settings_file::<AssistantSettings>(self.fs.clone(), cx, move |settings, _| { (self.on_model_changed)(model.clone(), cx);
settings.set_model(model.clone())
});
// Update the selection status // Update the selection status
let selected_model_id = model_info.model.id(); let selected_model_id = model_info.model.id();
@ -296,7 +297,7 @@ impl PickerDelegate for ModelPickerDelegate {
} }
} }
impl<T: PopoverTrigger> RenderOnce for ModelSelector<T> { impl<T: PopoverTrigger> RenderOnce for LanguageModelSelector<T> {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let selected_provider = LanguageModelRegistry::read_global(cx) let selected_provider = LanguageModelRegistry::read_global(cx)
.active_provider() .active_provider()
@ -331,8 +332,8 @@ impl<T: PopoverTrigger> RenderOnce for ModelSelector<T> {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let delegate = ModelPickerDelegate { let delegate = LanguageModelPickerDelegate {
fs: self.fs.clone(), on_model_changed: self.on_model_changed.clone(),
all_models: all_models.clone(), all_models: all_models.clone(),
filtered_models: all_models, filtered_models: all_models,
selected_index: 0, selected_index: 0,