assistant: Add model selector to the Context Editor (#25032)

This PR also removes everything related with the model selector from the
Context Editor toolbar.

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-02-17 16:29:42 -03:00 committed by GitHub
parent f833a01a7e
commit f2bc3d3738
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 94 additions and 107 deletions

View file

@ -587,6 +587,7 @@
"save": "workspace::Save", "save": "workspace::Save",
"ctrl->": "assistant::QuoteSelection", "ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor", "ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-/": "assistant::ToggleModelSelector",
"shift-enter": "assistant::Split", "shift-enter": "assistant::Split",
"ctrl-r": "assistant::CycleMessageRole", "ctrl-r": "assistant::CycleMessageRole",
"enter": "assistant::ConfirmCommand", "enter": "assistant::ConfirmCommand",

View file

@ -218,6 +218,7 @@
"cmd-s": "workspace::Save", "cmd-s": "workspace::Save",
"cmd->": "assistant::QuoteSelection", "cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor", "cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-/": "assistant::ToggleModelSelector",
"shift-enter": "assistant::Split", "shift-enter": "assistant::Split",
"ctrl-r": "assistant::CycleMessageRole", "ctrl-r": "assistant::CycleMessageRole",
"enter": "assistant::ConfirmCommand", "enter": "assistant::ConfirmCommand",

View file

@ -7,7 +7,7 @@ use assistant_context_editor::{
make_lsp_adapter_delegate, AssistantContext, AssistantPanelDelegate, ContextEditor, make_lsp_adapter_delegate, AssistantContext, AssistantPanelDelegate, ContextEditor,
ContextEditorToolbarItem, ContextEditorToolbarItemEvent, ContextHistory, ContextId, ContextEditorToolbarItem, ContextEditorToolbarItemEvent, ContextHistory, ContextId,
ContextStore, ContextStoreEvent, InsertDraggedFiles, SlashCommandCompletionProvider, ContextStore, ContextStoreEvent, InsertDraggedFiles, SlashCommandCompletionProvider,
ToggleModelSelector, DEFAULT_TAB_TITLE, DEFAULT_TAB_TITLE,
}; };
use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_settings::{AssistantDockPosition, AssistantSettings};
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
@ -21,7 +21,6 @@ use gpui::{
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID}; use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
use language_model_selector::LanguageModelSelector;
use project::Project; use project::Project;
use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary}; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
use search::{buffer_search::DivRegistrar, BufferSearchBar}; use search::{buffer_search::DivRegistrar, BufferSearchBar};
@ -29,7 +28,7 @@ use settings::{update_settings_file, Settings};
use smol::stream::StreamExt; use smol::stream::StreamExt;
use std::{ops::ControlFlow, path::PathBuf, sync::Arc}; use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip}; use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
use util::{maybe, ResultExt}; use util::{maybe, ResultExt};
use workspace::DraggedTab; use workspace::DraggedTab;
use workspace::{ use workspace::{
@ -77,7 +76,6 @@ 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<LanguageModelSelector>,
model_summary_editor: Entity<Editor>, model_summary_editor: Entity<Editor>,
authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>, authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
configuration_subscription: Option<Subscription>, configuration_subscription: Option<Subscription>,
@ -119,17 +117,9 @@ impl AssistantPanel {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
let model_selector_menu_handle = PopoverMenuHandle::default();
let model_summary_editor = cx.new(|cx| Editor::single_line(window, cx)); let model_summary_editor = cx.new(|cx| Editor::single_line(window, cx));
let context_editor_toolbar = cx.new(|cx| { let context_editor_toolbar =
ContextEditorToolbarItem::new( cx.new(|_| ContextEditorToolbarItem::new(model_summary_editor.clone()));
workspace,
model_selector_menu_handle.clone(),
model_summary_editor.clone(),
window,
cx,
)
});
let pane = cx.new(|cx| { let pane = cx.new(|cx| {
let mut pane = Pane::new( let mut pane = Pane::new(
@ -331,7 +321,6 @@ impl AssistantPanel {
languages: workspace.app_state().languages.clone(), languages: workspace.app_state().languages.clone(),
fs: workspace.app_state().fs.clone(), fs: workspace.app_state().fs.clone(),
subscriptions, subscriptions,
model_selector_menu_handle,
model_summary_editor, model_summary_editor,
authenticate_provider_task: None, authenticate_provider_task: None,
configuration_subscription: None, configuration_subscription: None,
@ -1054,15 +1043,6 @@ impl AssistantPanel {
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
fn toggle_model_selector(
&mut self,
_: &ToggleModelSelector,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.model_selector_menu_handle.toggle(window, cx);
}
pub(crate) fn active_context_editor(&self, cx: &App) -> Option<Entity<ContextEditor>> { pub(crate) fn active_context_editor(&self, cx: &App) -> Option<Entity<ContextEditor>> {
self.pane self.pane
.read(cx) .read(cx)
@ -1229,7 +1209,6 @@ impl Render for AssistantPanel {
})) }))
.on_action(cx.listener(AssistantPanel::deploy_history)) .on_action(cx.listener(AssistantPanel::deploy_history))
.on_action(cx.listener(AssistantPanel::deploy_prompt_library)) .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
.on_action(cx.listener(AssistantPanel::toggle_model_selector))
.child(registrar.size_full().child(self.pane.clone())) .child(registrar.size_full().child(self.pane.clone()))
.into_any_element() .into_any_element()
} }

View file

@ -193,6 +193,8 @@ pub struct ContextEditor {
// the file is opened. In order to keep the worktree alive for the duration of the // the file is opened. In order to keep the worktree alive for the duration of the
// context editor, we keep a reference here. // context editor, we keep a reference here.
dragged_file_worktrees: Vec<Entity<Worktree>>, dragged_file_worktrees: Vec<Entity<Worktree>>,
language_model_selector: Entity<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
} }
pub const DEFAULT_TAB_TITLE: &str = "New Chat"; pub const DEFAULT_TAB_TITLE: &str = "New Chat";
@ -238,6 +240,22 @@ impl ContextEditor {
cx.subscribe_in(&editor, window, Self::handle_editor_search_event), cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
]; ];
let fs_clone = fs.clone();
let language_model_selector = cx.new(|cx| {
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs_clone.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
},
window,
cx,
)
});
let language_model_selector_menu_handle = PopoverMenuHandle::default();
let sections = context.read(cx).slash_command_output_sections().to_vec(); let sections = context.read(cx).slash_command_output_sections().to_vec();
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>(); let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
let slash_commands = context.read(cx).slash_commands().clone(); let slash_commands = context.read(cx).slash_commands().clone();
@ -262,6 +280,8 @@ impl ContextEditor {
show_accept_terms: false, show_accept_terms: false,
slash_menu_handle: Default::default(), slash_menu_handle: Default::default(),
dragged_file_worktrees: Vec::new(), dragged_file_worktrees: Vec::new(),
language_model_selector,
language_model_selector_menu_handle,
}; };
this.update_message_headers(cx); this.update_message_headers(cx);
this.update_image_blocks(cx); this.update_image_blocks(cx);
@ -2355,15 +2375,64 @@ impl ContextEditor {
slash_command_picker::SlashCommandSelector::new( slash_command_picker::SlashCommandSelector::new(
self.slash_commands.clone(), self.slash_commands.clone(),
cx.entity().downgrade(), cx.entity().downgrade(),
Button::new("trigger", "Add Context") IconButton::new("trigger", IconName::Plus)
.icon(IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted),
.icon_position(IconPosition::Start), move |window, cx| {
Tooltip::text("Type / to insert via keyboard"), Tooltip::with_meta(
"Add Context",
None,
"Type / to insert via keyboard",
window,
cx,
)
},
) )
} }
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let focus_handle = self.editor().focus_handle(cx).clone();
let model_name = match active_model {
Some(model) => model.name().0,
None => SharedString::from("No model selected"),
};
LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.gap_0p5()
.child(
div().max_w_32().child(
Label::new(model_name)
.size(LabelSize::Small)
.color(Color::Muted)
.text_ellipsis()
.into_any_element(),
),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action_in(
"Change Model",
&ToggleModelSelector,
&focus_handle,
window,
cx,
)
},
)
.with_handle(self.language_model_selector_menu_handle.clone())
}
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> { fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
let last_error = self.last_error.as_ref()?; let last_error = self.last_error.as_ref()?;
@ -2800,6 +2869,7 @@ impl EventEmitter<SearchEvent> for ContextEditor {}
impl Render for ContextEditor { impl Render for ContextEditor {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let provider = LanguageModelRegistry::read_global(cx).active_provider(); let provider = LanguageModelRegistry::read_global(cx).active_provider();
let accept_terms = if self.show_accept_terms { let accept_terms = if self.show_accept_terms {
provider.as_ref().and_then(|provider| { provider.as_ref().and_then(|provider| {
provider.render_accept_terms(LanguageModelProviderTosView::PromptEditorPopup, cx) provider.render_accept_terms(LanguageModelProviderTosView::PromptEditorPopup, cx)
@ -2852,7 +2922,17 @@ impl Render for ContextEditor {
.border_t_1() .border_t_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.child(h_flex().gap_1().child(self.render_inject_context_menu(cx))) .child(
h_flex()
.gap_1()
.child(self.render_inject_context_menu(cx))
.child(ui::Divider::vertical())
.child(
div()
.pl_0p5()
.child(self.render_language_model_selector(cx)),
),
)
.child( .child(
h_flex() h_flex()
.w_full() .w_full()
@ -3163,36 +3243,13 @@ impl FollowableItem for ContextEditor {
pub struct ContextEditorToolbarItem { pub struct ContextEditorToolbarItem {
active_context_editor: Option<WeakEntity<ContextEditor>>, active_context_editor: Option<WeakEntity<ContextEditor>>,
model_summary_editor: Entity<Editor>, model_summary_editor: Entity<Editor>,
language_model_selector: Entity<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
} }
impl ContextEditorToolbarItem { impl ContextEditorToolbarItem {
pub fn new( pub fn new(model_summary_editor: Entity<Editor>) -> Self {
workspace: &Workspace,
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
model_summary_editor: Entity<Editor>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self { Self {
active_context_editor: None, active_context_editor: None,
model_summary_editor, model_summary_editor,
language_model_selector: cx.new(|cx| {
let fs = workspace.app_state().fs.clone();
LanguageModelSelector::new(
move |model, cx| {
update_settings_file::<AssistantSettings>(
fs.clone(),
cx,
move |settings, _| settings.set_model(model.clone()),
);
},
window,
cx,
)
}),
language_model_selector_menu_handle: model_selector_menu_handle,
} }
} }
@ -3263,8 +3320,7 @@ impl Render for ContextEditorToolbarItem {
})), })),
), ),
); );
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
let active_model = LanguageModelRegistry::read_global(cx).active_model();
let right_side = h_flex() let right_side = h_flex()
.gap_2() .gap_2()
// TODO display this in a nicer way, once we have a design for it. // TODO display this in a nicer way, once we have a design for it.
@ -3280,56 +3336,6 @@ impl Render for ContextEditorToolbarItem {
// scan_items_remaining // scan_items_remaining
// .map(|remaining_items| format!("Files to scan: {}", remaining_items)) // .map(|remaining_items| format!("Files to scan: {}", remaining_items))
// }) // })
.child(
LanguageModelSelectorPopoverMenu::new(
self.language_model_selector.clone(),
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.w_full()
.gap_0p5()
.child(
div()
.overflow_x_hidden()
.flex_grow()
.whitespace_nowrap()
.child(match (active_provider, active_model) {
(Some(provider), Some(model)) => h_flex()
.gap_1()
.child(
Icon::new(
model
.icon()
.unwrap_or_else(|| provider.icon()),
)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.child(
Label::new(model.name().0)
.size(LabelSize::Small)
.color(Color::Muted),
)
.into_any_element(),
_ => Label::new("No model selected")
.size(LabelSize::Small)
.color(Color::Muted)
.into_any_element(),
}),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
move |window, cx| {
Tooltip::for_action("Change Model", &ToggleModelSelector, window, cx)
},
)
.with_handle(self.language_model_selector_menu_handle.clone()),
)
.children(self.render_remaining_tokens(cx)); .children(self.render_remaining_tokens(cx));
h_flex() h_flex()