This PR solves my main pain point with Zed agent: I have a long list of available models from different providers, and I switch between a few of them depending on the context and the project. In particular, I use the same models from different providers depending on whether I'm working on a personal project or at my day job. Since I only care about a few models (none of which are in "recommended") that are scattered all over the list, switching between them is bothersome, even using search. This change adds a new option in `settings.json` (`agent.favorite_models`) and the UI to manipulate it directly from the list of available models. When any models are marked as favorites, they appear in a dedicated section at the very top of the list. Each model has a small icon button that appears on hover and allows to toggle whether it's marked as favorite. I implemented this on the UI level (i.e. there's no first-party knowledge about favorite models in the agent itself; in theory it could return favorite models as a group but it would make it harder to implement bespoke UI for the favorite models section and it also wouldn't work for text threads which don't use the ACP infrastructure). The feature is only enabled for the native agent but disabled for external agents because we can't easily map their model IDs to settings and there could be weird collisions between them. https://github.com/user-attachments/assets/cf23afe4-3883-45cb-9906-f55de3ea2a97 Closes https://github.com/zed-industries/zed/issues/31507 Release Notes: - Added the ability to mark language models as favorites and pin them to the top of the list. This feature is available in the native Zed agent (including text threads and the inline assistant), but not in external agents via ACP. --------- Co-authored-by: Danilo Leal <daniloleal09@gmail.com> Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de>
134 lines
4.7 KiB
Rust
134 lines
4.7 KiB
Rust
use crate::{
|
|
ModelUsageContext,
|
|
language_model_selector::{LanguageModelSelector, language_model_selector},
|
|
};
|
|
use fs::Fs;
|
|
use gpui::{Entity, FocusHandle, SharedString};
|
|
use picker::popover_menu::PickerPopoverMenu;
|
|
use settings::update_settings_file;
|
|
use std::sync::Arc;
|
|
use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
|
|
use zed_actions::agent::ToggleModelSelector;
|
|
|
|
pub struct AgentModelSelector {
|
|
selector: Entity<LanguageModelSelector>,
|
|
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
|
focus_handle: FocusHandle,
|
|
}
|
|
|
|
impl AgentModelSelector {
|
|
pub(crate) fn new(
|
|
fs: Arc<dyn Fs>,
|
|
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
|
focus_handle: FocusHandle,
|
|
model_usage_context: ModelUsageContext,
|
|
window: &mut Window,
|
|
cx: &mut Context<Self>,
|
|
) -> Self {
|
|
let focus_handle_clone = focus_handle.clone();
|
|
|
|
Self {
|
|
selector: cx.new(move |cx| {
|
|
language_model_selector(
|
|
{
|
|
let model_context = model_usage_context.clone();
|
|
move |cx| model_context.configured_model(cx)
|
|
},
|
|
{
|
|
let fs = fs.clone();
|
|
move |model, cx| {
|
|
let provider = model.provider_id().0.to_string();
|
|
let model_id = model.id().0.to_string();
|
|
match &model_usage_context {
|
|
ModelUsageContext::InlineAssistant => {
|
|
update_settings_file(fs.clone(), cx, move |settings, _cx| {
|
|
settings
|
|
.agent
|
|
.get_or_insert_default()
|
|
.set_inline_assistant_model(provider.clone(), model_id);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
let fs = fs.clone();
|
|
move |model, should_be_favorite, cx| {
|
|
crate::favorite_models::toggle_in_settings(
|
|
model,
|
|
should_be_favorite,
|
|
fs.clone(),
|
|
cx,
|
|
);
|
|
}
|
|
},
|
|
true, // Use popover styles for picker
|
|
focus_handle_clone,
|
|
window,
|
|
cx,
|
|
)
|
|
}),
|
|
menu_handle,
|
|
focus_handle,
|
|
}
|
|
}
|
|
|
|
pub fn toggle(&self, window: &mut Window, cx: &mut Context<Self>) {
|
|
self.menu_handle.toggle(window, cx);
|
|
}
|
|
|
|
pub fn active_model(&self, cx: &App) -> Option<language_model::ConfiguredModel> {
|
|
self.selector.read(cx).delegate.active_model(cx)
|
|
}
|
|
}
|
|
|
|
impl Render for AgentModelSelector {
|
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
let model = self.selector.read(cx).delegate.active_model(cx);
|
|
let model_name = model
|
|
.as_ref()
|
|
.map(|model| model.model.name().0)
|
|
.unwrap_or_else(|| SharedString::from("Select a Model"));
|
|
|
|
let provider_icon = model.as_ref().map(|model| model.provider.icon());
|
|
let color = if self.menu_handle.is_deployed() {
|
|
Color::Accent
|
|
} else {
|
|
Color::Muted
|
|
};
|
|
|
|
let focus_handle = self.focus_handle.clone();
|
|
|
|
PickerPopoverMenu::new(
|
|
self.selector.clone(),
|
|
ButtonLike::new("active-model")
|
|
.when_some(provider_icon, |this, icon| {
|
|
this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
|
|
})
|
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
|
.child(
|
|
Label::new(model_name)
|
|
.color(color)
|
|
.size(LabelSize::Small)
|
|
.ml_0p5(),
|
|
)
|
|
.child(
|
|
Icon::new(IconName::ChevronDown)
|
|
.color(color)
|
|
.size(IconSize::Small),
|
|
),
|
|
move |_window, cx| {
|
|
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
|
},
|
|
gpui::Corner::TopRight,
|
|
cx,
|
|
)
|
|
.with_handle(self.menu_handle.clone())
|
|
.offset(gpui::Point {
|
|
x: px(0.0),
|
|
y: px(2.0),
|
|
})
|
|
.render(window, cx)
|
|
}
|
|
}
|