From 3c81ee6ba647f1ae7b172c0a259ce95e7dbd4af3 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:12:13 -0300 Subject: [PATCH] agent_ui: Allow to configure a default model for profiles through modal (#42359) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to https://github.com/zed-industries/zed/pull/39220 This PR allows to configure a default model for a given profile through the profile management modal. | Option In Picker | Model Selector | |--------|--------| | Screenshot 2025-11-10 at 12  24
2@2x | Screenshot 2025-11-10 at 12 
24@2x | Release Notes: - N/A --- .../manage_profiles_modal.rs | 161 +++++++++++++++++- crates/agent_ui/src/agent_model_selector.rs | 1 + .../agent_ui/src/language_model_selector.rs | 29 +++- crates/agent_ui/src/text_thread_editor.rs | 1 + 4 files changed, 185 insertions(+), 7 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs b/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs index e583bb7d5425ec4c6f233ac0eed67c358ccac98d..210cf5f5dd6612855b32e358a2d3ec38e8259373 100644 --- a/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs +++ b/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs @@ -7,8 +7,10 @@ use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profil use editor::Editor; use fs::Fs; use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription, prelude::*}; -use language_model::LanguageModel; -use settings::Settings as _; +use language_model::{LanguageModel, LanguageModelRegistry}; +use settings::{ + LanguageModelProviderSetting, LanguageModelSelection, Settings as _, update_settings_file, +}; use ui::{ KeyBinding, ListItem, ListItemSpacing, ListSeparator, Navigable, NavigableEntry, prelude::*, }; @@ -16,6 +18,7 @@ use workspace::{ModalView, Workspace}; use crate::agent_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader; use crate::agent_configuration::tool_picker::{ToolPicker, ToolPickerDelegate}; +use crate::language_model_selector::{LanguageModelSelector, language_model_selector}; use crate::{AgentPanel, ManageProfiles}; enum Mode { @@ -32,6 +35,11 @@ enum Mode { tool_picker: Entity, _subscription: Subscription, }, + ConfigureDefaultModel { + profile_id: AgentProfileId, + model_picker: Entity, + _subscription: Subscription, + }, } impl Mode { @@ -83,6 +91,7 @@ pub struct ChooseProfileMode { pub struct ViewProfileMode { profile_id: AgentProfileId, fork_profile: NavigableEntry, + configure_default_model: NavigableEntry, configure_tools: NavigableEntry, configure_mcps: NavigableEntry, cancel_item: NavigableEntry, @@ -180,6 +189,7 @@ impl ManageProfilesModal { self.mode = Mode::ViewProfile(ViewProfileMode { profile_id, fork_profile: NavigableEntry::focusable(cx), + configure_default_model: NavigableEntry::focusable(cx), configure_tools: NavigableEntry::focusable(cx), configure_mcps: NavigableEntry::focusable(cx), cancel_item: NavigableEntry::focusable(cx), @@ -187,6 +197,83 @@ impl ManageProfilesModal { self.focus_handle(cx).focus(window); } + fn configure_default_model( + &mut self, + profile_id: AgentProfileId, + window: &mut Window, + cx: &mut Context, + ) { + let fs = self.fs.clone(); + let profile_id_for_closure = profile_id.clone(); + + let model_picker = cx.new(|cx| { + let fs = fs.clone(); + let profile_id = profile_id_for_closure.clone(); + + language_model_selector( + { + let profile_id = profile_id.clone(); + move |cx| { + let settings = AgentSettings::get_global(cx); + + settings + .profiles + .get(&profile_id) + .and_then(|profile| profile.default_model.as_ref()) + .and_then(|selection| { + let registry = LanguageModelRegistry::read_global(cx); + let provider_id = language_model::LanguageModelProviderId( + gpui::SharedString::from(selection.provider.0.clone()), + ); + let provider = registry.provider(&provider_id)?; + let model = provider + .provided_models(cx) + .iter() + .find(|m| m.id().0 == selection.model.as_str())? + .clone(); + Some(language_model::ConfiguredModel { provider, model }) + }) + } + }, + move |model, cx| { + let provider = model.provider_id().0.to_string(); + let model_id = model.id().0.to_string(); + let profile_id = profile_id.clone(); + + update_settings_file(fs.clone(), cx, move |settings, _cx| { + let agent_settings = settings.agent.get_or_insert_default(); + if let Some(profiles) = agent_settings.profiles.as_mut() { + if let Some(profile) = profiles.get_mut(profile_id.0.as_ref()) { + profile.default_model = Some(LanguageModelSelection { + provider: LanguageModelProviderSetting(provider.clone()), + model: model_id.clone(), + }); + } + } + }); + }, + false, // Do not use popover styles for the model picker + window, + cx, + ) + .modal(false) + }); + + let dismiss_subscription = cx.subscribe_in(&model_picker, window, { + let profile_id = profile_id.clone(); + move |this, _picker, _: &DismissEvent, window, cx| { + this.view_profile(profile_id.clone(), window, cx); + } + }); + + self.mode = Mode::ConfigureDefaultModel { + profile_id, + model_picker, + _subscription: dismiss_subscription, + }; + self.focus_handle(cx).focus(window); + } + fn configure_mcp_tools( &mut self, profile_id: AgentProfileId, @@ -277,6 +364,7 @@ impl ManageProfilesModal { Mode::ViewProfile(_) => {} Mode::ConfigureTools { .. } => {} Mode::ConfigureMcps { .. } => {} + Mode::ConfigureDefaultModel { .. } => {} } } @@ -299,6 +387,9 @@ impl ManageProfilesModal { Mode::ConfigureMcps { profile_id, .. } => { self.view_profile(profile_id.clone(), window, cx) } + Mode::ConfigureDefaultModel { profile_id, .. } => { + self.view_profile(profile_id.clone(), window, cx) + } } } } @@ -313,6 +404,7 @@ impl Focusable for ManageProfilesModal { Mode::ViewProfile(_) => self.focus_handle.clone(), Mode::ConfigureTools { tool_picker, .. } => tool_picker.focus_handle(cx), Mode::ConfigureMcps { tool_picker, .. } => tool_picker.focus_handle(cx), + Mode::ConfigureDefaultModel { model_picker, .. } => model_picker.focus_handle(cx), } } } @@ -544,6 +636,47 @@ impl ManageProfilesModal { }), ), ) + .child( + div() + .id("configure-default-model") + .track_focus(&mode.configure_default_model.focus_handle) + .on_action({ + let profile_id = mode.profile_id.clone(); + cx.listener(move |this, _: &menu::Confirm, window, cx| { + this.configure_default_model( + profile_id.clone(), + window, + cx, + ); + }) + }) + .child( + ListItem::new("model-item") + .toggle_state( + mode.configure_default_model + .focus_handle + .contains_focused(window, cx), + ) + .inset(true) + .spacing(ListItemSpacing::Sparse) + .start_slot( + Icon::new(IconName::ZedAssistant) + .size(IconSize::Small) + .color(Color::Muted), + ) + .child(Label::new("Configure Default Model")) + .on_click({ + let profile_id = mode.profile_id.clone(); + cx.listener(move |this, _, window, cx| { + this.configure_default_model( + profile_id.clone(), + window, + cx, + ); + }) + }), + ), + ) .child( div() .id("configure-builtin-tools") @@ -668,6 +801,7 @@ impl ManageProfilesModal { .into_any_element(), ) .entry(mode.fork_profile) + .entry(mode.configure_default_model) .entry(mode.configure_tools) .entry(mode.configure_mcps) .entry(mode.cancel_item) @@ -753,6 +887,29 @@ impl Render for ManageProfilesModal { .child(go_back_item) .into_any_element() } + Mode::ConfigureDefaultModel { + profile_id, + model_picker, + .. + } => { + let profile_name = settings + .profiles + .get(profile_id) + .map(|profile| profile.name.clone()) + .unwrap_or_else(|| "Unknown".into()); + + v_flex() + .pb_1() + .child(ProfileModalHeader::new( + format!("{profile_name} — Configure Default Model"), + Some(IconName::Ai), + )) + .child(ListSeparator) + .child(v_flex().w(rems(34.)).child(model_picker.clone())) + .child(ListSeparator) + .child(go_back_item) + .into_any_element() + } Mode::ConfigureMcps { profile_id, tool_picker, diff --git a/crates/agent_ui/src/agent_model_selector.rs b/crates/agent_ui/src/agent_model_selector.rs index df7d166064da20aa4bc958ebd6a9df806164eb7a..900ca0b683670a30b3353655d17c2ef79cd5523b 100644 --- a/crates/agent_ui/src/agent_model_selector.rs +++ b/crates/agent_ui/src/agent_model_selector.rs @@ -47,6 +47,7 @@ impl AgentModelSelector { } } }, + true, // Use popover styles for picker window, cx, ) diff --git a/crates/agent_ui/src/language_model_selector.rs b/crates/agent_ui/src/language_model_selector.rs index c8838fd9bc8b33ccb87f3198126c5c470328c810..0f7b83e3edba6c8d97c2c12a939a65cb71c39dca 100644 --- a/crates/agent_ui/src/language_model_selector.rs +++ b/crates/agent_ui/src/language_model_selector.rs @@ -19,14 +19,26 @@ pub type LanguageModelSelector = Picker; pub fn language_model_selector( get_active_model: impl Fn(&App) -> Option + 'static, on_model_changed: impl Fn(Arc, &mut App) + 'static, + popover_styles: bool, window: &mut Window, cx: &mut Context, ) -> LanguageModelSelector { - let delegate = LanguageModelPickerDelegate::new(get_active_model, on_model_changed, window, cx); - Picker::list(delegate, window, cx) - .show_scrollbar(true) - .width(rems(20.)) - .max_height(Some(rems(20.).into())) + let delegate = LanguageModelPickerDelegate::new( + get_active_model, + on_model_changed, + popover_styles, + window, + cx, + ); + + if popover_styles { + Picker::list(delegate, window, cx) + .show_scrollbar(true) + .width(rems(20.)) + .max_height(Some(rems(20.).into())) + } else { + Picker::list(delegate, window, cx).show_scrollbar(true) + } } fn all_models(cx: &App) -> GroupedModels { @@ -75,12 +87,14 @@ pub struct LanguageModelPickerDelegate { selected_index: usize, _authenticate_all_providers_task: Task<()>, _subscriptions: Vec, + popover_styles: bool, } impl LanguageModelPickerDelegate { fn new( get_active_model: impl Fn(&App) -> Option + 'static, on_model_changed: impl Fn(Arc, &mut App) + 'static, + popover_styles: bool, window: &mut Window, cx: &mut Context>, ) -> Self { @@ -113,6 +127,7 @@ impl LanguageModelPickerDelegate { } }, )], + popover_styles, } } @@ -530,6 +545,10 @@ impl PickerDelegate for LanguageModelPickerDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { + if !self.popover_styles { + return None; + } + Some( h_flex() .w_full() diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index be77ec05fe3afbecab5373bd97ec23ee77dd614a..19063075f9cf7382270c4dbaf4930596a7592676 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -314,6 +314,7 @@ impl TextThreadEditor { ) }); }, + true, // Use popover styles for picker window, cx, )