From f2bc3d3738f0b755a20439d8063d57cf87302575 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:29:42 -0300 Subject: [PATCH] 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 --- assets/keymaps/default-linux.json | 1 + assets/keymaps/default-macos.json | 1 + crates/assistant/src/assistant_panel.rs | 29 +-- .../src/context_editor.rs | 170 +++++++++--------- 4 files changed, 94 insertions(+), 107 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index bdfd75f4f2..75090203be 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -587,6 +587,7 @@ "save": "workspace::Save", "ctrl->": "assistant::QuoteSelection", "ctrl-<": "assistant::InsertIntoEditor", + "ctrl-alt-/": "assistant::ToggleModelSelector", "shift-enter": "assistant::Split", "ctrl-r": "assistant::CycleMessageRole", "enter": "assistant::ConfirmCommand", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 98ad590eb5..8e055677fd 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -218,6 +218,7 @@ "cmd-s": "workspace::Save", "cmd->": "assistant::QuoteSelection", "cmd-<": "assistant::InsertIntoEditor", + "cmd-alt-/": "assistant::ToggleModelSelector", "shift-enter": "assistant::Split", "ctrl-r": "assistant::CycleMessageRole", "enter": "assistant::ConfirmCommand", diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 82fb1dd83d..585f809e25 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -7,7 +7,7 @@ use assistant_context_editor::{ make_lsp_adapter_delegate, AssistantContext, AssistantPanelDelegate, ContextEditor, ContextEditorToolbarItem, ContextEditorToolbarItemEvent, ContextHistory, ContextId, ContextStore, ContextStoreEvent, InsertDraggedFiles, SlashCommandCompletionProvider, - ToggleModelSelector, DEFAULT_TAB_TITLE, + DEFAULT_TAB_TITLE, }; use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_slash_command::SlashCommandWorkingSet; @@ -21,7 +21,6 @@ use gpui::{ }; use language::LanguageRegistry; use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID}; -use language_model_selector::LanguageModelSelector; use project::Project; use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary}; use search::{buffer_search::DivRegistrar, BufferSearchBar}; @@ -29,7 +28,7 @@ use settings::{update_settings_file, Settings}; use smol::stream::StreamExt; use std::{ops::ControlFlow, path::PathBuf, sync::Arc}; 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 workspace::DraggedTab; use workspace::{ @@ -77,7 +76,6 @@ pub struct AssistantPanel { languages: Arc, fs: Arc, subscriptions: Vec, - model_selector_menu_handle: PopoverMenuHandle, model_summary_editor: Entity, authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>, configuration_subscription: Option, @@ -119,17 +117,9 @@ impl AssistantPanel { window: &mut Window, cx: &mut Context, ) -> Self { - let model_selector_menu_handle = PopoverMenuHandle::default(); let model_summary_editor = cx.new(|cx| Editor::single_line(window, cx)); - let context_editor_toolbar = cx.new(|cx| { - ContextEditorToolbarItem::new( - workspace, - model_selector_menu_handle.clone(), - model_summary_editor.clone(), - window, - cx, - ) - }); + let context_editor_toolbar = + cx.new(|_| ContextEditorToolbarItem::new(model_summary_editor.clone())); let pane = cx.new(|cx| { let mut pane = Pane::new( @@ -331,7 +321,6 @@ impl AssistantPanel { languages: workspace.app_state().languages.clone(), fs: workspace.app_state().fs.clone(), subscriptions, - model_selector_menu_handle, model_summary_editor, authenticate_provider_task: None, configuration_subscription: None, @@ -1054,15 +1043,6 @@ impl AssistantPanel { .detach_and_log_err(cx); } - fn toggle_model_selector( - &mut self, - _: &ToggleModelSelector, - window: &mut Window, - cx: &mut Context, - ) { - self.model_selector_menu_handle.toggle(window, cx); - } - pub(crate) fn active_context_editor(&self, cx: &App) -> Option> { self.pane .read(cx) @@ -1229,7 +1209,6 @@ impl Render for AssistantPanel { })) .on_action(cx.listener(AssistantPanel::deploy_history)) .on_action(cx.listener(AssistantPanel::deploy_prompt_library)) - .on_action(cx.listener(AssistantPanel::toggle_model_selector)) .child(registrar.size_full().child(self.pane.clone())) .into_any_element() } diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index bc1294e861..24cfe23596 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -193,6 +193,8 @@ pub struct ContextEditor { // the file is opened. In order to keep the worktree alive for the duration of the // context editor, we keep a reference here. dragged_file_worktrees: Vec>, + language_model_selector: Entity, + language_model_selector_menu_handle: PopoverMenuHandle, } pub const DEFAULT_TAB_TITLE: &str = "New Chat"; @@ -238,6 +240,22 @@ impl ContextEditor { 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::( + 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 patch_ranges = context.read(cx).patch_ranges().collect::>(); let slash_commands = context.read(cx).slash_commands().clone(); @@ -262,6 +280,8 @@ impl ContextEditor { show_accept_terms: false, slash_menu_handle: Default::default(), dragged_file_worktrees: Vec::new(), + language_model_selector, + language_model_selector_menu_handle, }; this.update_message_headers(cx); this.update_image_blocks(cx); @@ -2355,15 +2375,64 @@ impl ContextEditor { slash_command_picker::SlashCommandSelector::new( self.slash_commands.clone(), cx.entity().downgrade(), - Button::new("trigger", "Add Context") - .icon(IconName::Plus) + IconButton::new("trigger", IconName::Plus) .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start), - Tooltip::text("Type / to insert via keyboard"), + .icon_color(Color::Muted), + move |window, cx| { + Tooltip::with_meta( + "Add Context", + None, + "Type / to insert via keyboard", + window, + cx, + ) + }, ) } + fn render_language_model_selector(&self, cx: &mut Context) -> 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) -> Option { let last_error = self.last_error.as_ref()?; @@ -2800,6 +2869,7 @@ impl EventEmitter for ContextEditor {} impl Render for ContextEditor { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let provider = LanguageModelRegistry::read_global(cx).active_provider(); + let accept_terms = if self.show_accept_terms { provider.as_ref().and_then(|provider| { provider.render_accept_terms(LanguageModelProviderTosView::PromptEditorPopup, cx) @@ -2852,7 +2922,17 @@ impl Render for ContextEditor { .border_t_1() .border_color(cx.theme().colors().border_variant) .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( h_flex() .w_full() @@ -3163,36 +3243,13 @@ impl FollowableItem for ContextEditor { pub struct ContextEditorToolbarItem { active_context_editor: Option>, model_summary_editor: Entity, - language_model_selector: Entity, - language_model_selector_menu_handle: PopoverMenuHandle, } impl ContextEditorToolbarItem { - pub fn new( - workspace: &Workspace, - model_selector_menu_handle: PopoverMenuHandle, - model_summary_editor: Entity, - window: &mut Window, - cx: &mut Context, - ) -> Self { + pub fn new(model_summary_editor: Entity) -> Self { Self { active_context_editor: None, model_summary_editor, - language_model_selector: cx.new(|cx| { - let fs = workspace.app_state().fs.clone(); - LanguageModelSelector::new( - move |model, cx| { - update_settings_file::( - 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() .gap_2() // 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 // .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)); h_flex()