diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs index 63ca65f22725c54476048542c90f5f5efcfd23ca..80bec0ee9d351711bdf435cfe63eb99eb1e499e3 100644 --- a/crates/acp_thread/src/connection.rs +++ b/crates/acp_thread/src/connection.rs @@ -197,6 +197,11 @@ pub trait AgentModelSelector: 'static { fn watch(&self, _cx: &mut App) -> Option> { None } + + /// Returns whether the model picker should render a footer. + fn should_render_footer(&self) -> bool { + false + } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index a85c01bb225ab58a372a8b09fb07e7bc155a7aeb..404cd6549e5786b92c49379918346b83fcc0e0c1 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -961,6 +961,10 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector { fn watch(&self, cx: &mut App) -> Option> { Some(self.connection.0.read(cx).models.watch()) } + + fn should_render_footer(&self) -> bool { + true + } } impl acp_thread::AgentConnection for NativeAgentConnection { diff --git a/crates/agent_ui/src/acp/model_selector.rs b/crates/agent_ui/src/acp/model_selector.rs index c60a3b6cb61970caba02df82506848b6efa90cc1..8a0c3c9df90e73d0ef00ecd7232115729dd35347 100644 --- a/crates/agent_ui/src/acp/model_selector.rs +++ b/crates/agent_ui/src/acp/model_selector.rs @@ -7,14 +7,17 @@ use collections::IndexMap; use fs::Fs; use futures::FutureExt; use fuzzy::{StringMatchCandidate, match_strings}; -use gpui::{AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity}; +use gpui::{ + Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, FocusHandle, Task, WeakEntity, +}; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; use ui::{ - DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, ListItem, + DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, KeyBinding, ListItem, ListItemSpacing, prelude::*, }; use util::ResultExt; +use zed_actions::agent::OpenSettings; use crate::ui::HoldForDefault; @@ -24,10 +27,12 @@ pub fn acp_model_selector( selector: Rc, agent_server: Rc, fs: Arc, + focus_handle: FocusHandle, window: &mut Window, cx: &mut Context, ) -> AcpModelSelector { - let delegate = AcpModelPickerDelegate::new(selector, agent_server, fs, window, cx); + let delegate = + AcpModelPickerDelegate::new(selector, agent_server, fs, focus_handle, window, cx); Picker::list(delegate, window, cx) .show_scrollbar(true) .width(rems(20.)) @@ -49,6 +54,7 @@ pub struct AcpModelPickerDelegate { selected_description: Option<(usize, SharedString, bool)>, selected_model: Option, _refresh_models_task: Task<()>, + focus_handle: FocusHandle, } impl AcpModelPickerDelegate { @@ -56,6 +62,7 @@ impl AcpModelPickerDelegate { selector: Rc, agent_server: Rc, fs: Arc, + focus_handle: FocusHandle, window: &mut Window, cx: &mut Context, ) -> Self { @@ -104,6 +111,7 @@ impl AcpModelPickerDelegate { selected_index: 0, selected_description: None, _refresh_models_task: refresh_models_task, + focus_handle, } } @@ -331,6 +339,39 @@ impl PickerDelegate for AcpModelPickerDelegate { ) }) } + + fn render_footer( + &self, + _window: &mut Window, + cx: &mut Context>, + ) -> Option { + let focus_handle = self.focus_handle.clone(); + + if !self.selector.should_render_footer() { + return None; + } + + Some( + h_flex() + .w_full() + .p_1p5() + .border_t_1() + .border_color(cx.theme().colors().border_variant) + .child( + Button::new("configure", "Configure") + .full_width() + .style(ButtonStyle::Outlined) + .key_binding( + KeyBinding::for_action_in(&OpenSettings, &focus_handle, cx) + .map(|kb| kb.size(rems_from_px(12.))), + ) + .on_click(|_, window, cx| { + window.dispatch_action(OpenSettings.boxed_clone(), cx); + }), + ) + .into_any(), + ) + } } fn info_list_to_picker_entries( diff --git a/crates/agent_ui/src/acp/model_selector_popover.rs b/crates/agent_ui/src/acp/model_selector_popover.rs index 04e7e06a85aadf7c7fb1b69bfcaf81ec6ff6bf89..e2393c11bd6c23b79397abf274fb6539c0c7063f 100644 --- a/crates/agent_ui/src/acp/model_selector_popover.rs +++ b/crates/agent_ui/src/acp/model_selector_popover.rs @@ -30,8 +30,18 @@ impl AcpModelSelectorPopover { window: &mut Window, cx: &mut Context, ) -> Self { + let focus_handle_clone = focus_handle.clone(); Self { - selector: cx.new(move |cx| acp_model_selector(selector, agent_server, fs, window, cx)), + selector: cx.new(move |cx| { + acp_model_selector( + selector, + agent_server, + fs, + focus_handle_clone.clone(), + window, + cx, + ) + }), menu_handle, focus_handle, } 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 210cf5f5dd6612855b32e358a2d3ec38e8259373..7e03dc46b704c22b4665bbce0f3b818134b56634 100644 --- a/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs +++ b/crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs @@ -253,6 +253,7 @@ impl ManageProfilesModal { }); }, false, // Do not use popover styles for the model picker + self.focus_handle.clone(), window, cx, ) diff --git a/crates/agent_ui/src/agent_model_selector.rs b/crates/agent_ui/src/agent_model_selector.rs index 900ca0b683670a30b3353655d17c2ef79cd5523b..43982cdda7bd887b8fd9970e836090a0e549ae11 100644 --- a/crates/agent_ui/src/agent_model_selector.rs +++ b/crates/agent_ui/src/agent_model_selector.rs @@ -25,6 +25,8 @@ impl AgentModelSelector { window: &mut Window, cx: &mut Context, ) -> Self { + let focus_handle_clone = focus_handle.clone(); + Self { selector: cx.new(move |cx| { let fs = fs.clone(); @@ -48,6 +50,7 @@ impl AgentModelSelector { } }, true, // Use popover styles for picker + focus_handle_clone, window, cx, ) diff --git a/crates/agent_ui/src/language_model_selector.rs b/crates/agent_ui/src/language_model_selector.rs index 996e6a19828c741adbf6f8f824470f9a66c2f049..5b5a4513c6dca32e985c966e07ad84e84fc9a872 100644 --- a/crates/agent_ui/src/language_model_selector.rs +++ b/crates/agent_ui/src/language_model_selector.rs @@ -2,14 +2,17 @@ use std::{cmp::Reverse, sync::Arc}; use collections::IndexMap; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; -use gpui::{Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task}; +use gpui::{ + Action, AnyElement, App, BackgroundExecutor, DismissEvent, FocusHandle, Subscription, Task, +}; use language_model::{ AuthenticateError, ConfiguredModel, LanguageModel, LanguageModelProviderId, LanguageModelRegistry, }; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; -use ui::{ListItem, ListItemSpacing, prelude::*}; +use ui::{KeyBinding, ListItem, ListItemSpacing, prelude::*}; +use zed_actions::agent::OpenSettings; type OnModelChanged = Arc, &mut App) + 'static>; type GetActiveModel = Arc Option + 'static>; @@ -20,6 +23,7 @@ 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, + focus_handle: FocusHandle, window: &mut Window, cx: &mut Context, ) -> LanguageModelSelector { @@ -27,6 +31,7 @@ pub fn language_model_selector( get_active_model, on_model_changed, popover_styles, + focus_handle, window, cx, ); @@ -88,6 +93,7 @@ pub struct LanguageModelPickerDelegate { _authenticate_all_providers_task: Task<()>, _subscriptions: Vec, popover_styles: bool, + focus_handle: FocusHandle, } impl LanguageModelPickerDelegate { @@ -95,6 +101,7 @@ impl LanguageModelPickerDelegate { get_active_model: impl Fn(&App) -> Option + 'static, on_model_changed: impl Fn(Arc, &mut App) + 'static, popover_styles: bool, + focus_handle: FocusHandle, window: &mut Window, cx: &mut Context>, ) -> Self { @@ -128,6 +135,7 @@ impl LanguageModelPickerDelegate { }, )], popover_styles, + focus_handle, } } @@ -521,6 +529,8 @@ impl PickerDelegate for LanguageModelPickerDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { + let focus_handle = self.focus_handle.clone(); + if !self.popover_styles { return None; } @@ -528,22 +538,19 @@ impl PickerDelegate for LanguageModelPickerDelegate { Some( h_flex() .w_full() + .p_1p5() .border_t_1() .border_color(cx.theme().colors().border_variant) - .p_1() - .gap_4() - .justify_between() .child( Button::new("configure", "Configure") - .icon(IconName::Settings) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) + .full_width() + .style(ButtonStyle::Outlined) + .key_binding( + KeyBinding::for_action_in(&OpenSettings, &focus_handle, cx) + .map(|kb| kb.size(rems_from_px(12.))), + ) .on_click(|_, window, cx| { - window.dispatch_action( - zed_actions::agent::OpenSettings.boxed_clone(), - cx, - ); + window.dispatch_action(OpenSettings.boxed_clone(), cx); }), ) .into_any(), diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 2a3c7e10318da78729f35476da872a0651c4a145..8c245a0675a03e65efeaf3e92bc3b7a5062fdd53 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -280,6 +280,8 @@ impl TextThreadEditor { .thought_process_output_sections() .to_vec(); let slash_commands = text_thread.read(cx).slash_commands().clone(); + let focus_handle = editor.read(cx).focus_handle(cx); + let mut this = Self { text_thread, slash_commands, @@ -315,6 +317,7 @@ impl TextThreadEditor { }); }, true, // Use popover styles for picker + focus_handle, window, cx, )