agent_ui: Make input fields in Bedrock settings keyboard navigable (#42916)
Closes https://github.com/zed-industries/zed/issues/36587 This PR enables jumping from one input to the other, in the Bedrock settings section, with tab. Release Notes: - N/A
This commit is contained in:
@@ -24,7 +24,10 @@ use bedrock::{
|
||||
use collections::{BTreeMap, HashMap};
|
||||
use credentials_provider::CredentialsProvider;
|
||||
use futures::{FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream};
|
||||
use gpui::{AnyView, App, AsyncApp, Context, Entity, FontWeight, Subscription, Task};
|
||||
use gpui::{
|
||||
AnyView, App, AsyncApp, Context, Entity, FocusHandle, FontWeight, Subscription, Task, Window,
|
||||
actions,
|
||||
};
|
||||
use gpui_tokio::Tokio;
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
@@ -47,6 +50,8 @@ use util::ResultExt;
|
||||
|
||||
use crate::AllLanguageModelSettings;
|
||||
|
||||
actions!(bedrock, [Tab, TabPrev]);
|
||||
|
||||
const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("amazon-bedrock");
|
||||
const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("Amazon Bedrock");
|
||||
|
||||
@@ -1012,6 +1017,7 @@ struct ConfigurationView {
|
||||
region_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
@@ -1022,11 +1028,41 @@ impl ConfigurationView {
|
||||
const PLACEHOLDER_REGION: &'static str = "us-east-1";
|
||||
|
||||
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
cx.observe(&state, |_, _, cx| {
|
||||
cx.notify();
|
||||
})
|
||||
.detach();
|
||||
|
||||
let access_key_id_editor = cx.new(|cx| {
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_ACCESS_KEY_ID_TEXT)
|
||||
.label("Access Key ID")
|
||||
.tab_index(0)
|
||||
.tab_stop(true)
|
||||
});
|
||||
|
||||
let secret_access_key_editor = cx.new(|cx| {
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_SECRET_ACCESS_KEY_TEXT)
|
||||
.label("Secret Access Key")
|
||||
.tab_index(1)
|
||||
.tab_stop(true)
|
||||
});
|
||||
|
||||
let session_token_editor = cx.new(|cx| {
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_SESSION_TOKEN_TEXT)
|
||||
.label("Session Token (Optional)")
|
||||
.tab_index(2)
|
||||
.tab_stop(true)
|
||||
});
|
||||
|
||||
let region_editor = cx.new(|cx| {
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_REGION)
|
||||
.label("Region")
|
||||
.tab_index(3)
|
||||
.tab_stop(true)
|
||||
});
|
||||
|
||||
let load_credentials_task = Some(cx.spawn({
|
||||
let state = state.clone();
|
||||
async move |this, cx| {
|
||||
@@ -1046,22 +1082,13 @@ impl ConfigurationView {
|
||||
}));
|
||||
|
||||
Self {
|
||||
access_key_id_editor: cx.new(|cx| {
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_ACCESS_KEY_ID_TEXT)
|
||||
.label("Access Key ID")
|
||||
}),
|
||||
secret_access_key_editor: cx.new(|cx| {
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_SECRET_ACCESS_KEY_TEXT)
|
||||
.label("Secret Access Key")
|
||||
}),
|
||||
session_token_editor: cx.new(|cx| {
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_SESSION_TOKEN_TEXT)
|
||||
.label("Session Token (Optional)")
|
||||
}),
|
||||
region_editor: cx
|
||||
.new(|cx| InputField::new(window, cx, Self::PLACEHOLDER_REGION).label("Region")),
|
||||
access_key_id_editor,
|
||||
secret_access_key_editor,
|
||||
session_token_editor,
|
||||
region_editor,
|
||||
state,
|
||||
load_credentials_task,
|
||||
focus_handle,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1141,6 +1168,19 @@ impl ConfigurationView {
|
||||
fn should_render_editor(&self, cx: &Context<Self>) -> bool {
|
||||
self.state.read(cx).is_authenticated()
|
||||
}
|
||||
|
||||
fn on_tab(&mut self, _: &menu::SelectNext, window: &mut Window, _: &mut Context<Self>) {
|
||||
window.focus_next();
|
||||
}
|
||||
|
||||
fn on_tab_prev(
|
||||
&mut self,
|
||||
_: &menu::SelectPrevious,
|
||||
window: &mut Window,
|
||||
_: &mut Context<Self>,
|
||||
) {
|
||||
window.focus_prev();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ConfigurationView {
|
||||
@@ -1190,6 +1230,9 @@ impl Render for ConfigurationView {
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_action(cx.listener(Self::on_tab))
|
||||
.on_action(cx.listener(Self::on_tab_prev))
|
||||
.on_action(cx.listener(ConfigurationView::save_credentials))
|
||||
.child(Label::new("To use Zed's agent with Bedrock, you can set a custom authentication strategy through the settings.json, or use static credentials."))
|
||||
.child(Label::new("But, to access models on AWS, you need to:").mt_1())
|
||||
@@ -1234,6 +1277,7 @@ impl ConfigurationView {
|
||||
fn render_static_credentials_ui(&self) -> impl IntoElement {
|
||||
v_flex()
|
||||
.my_2()
|
||||
.tab_group()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new("Static Keys")
|
||||
|
||||
@@ -37,6 +37,10 @@ pub struct InputField {
|
||||
disabled: bool,
|
||||
/// The minimum width of for the input
|
||||
min_width: Length,
|
||||
/// The tab index for keyboard navigation order.
|
||||
tab_index: Option<isize>,
|
||||
/// Whether this field is a tab stop (can be focused via Tab key).
|
||||
tab_stop: bool,
|
||||
}
|
||||
|
||||
impl Focusable for InputField {
|
||||
@@ -63,6 +67,8 @@ impl InputField {
|
||||
start_icon: None,
|
||||
disabled: false,
|
||||
min_width: px(192.).into(),
|
||||
tab_index: None,
|
||||
tab_stop: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +92,16 @@ impl InputField {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tab_index(mut self, index: isize) -> Self {
|
||||
self.tab_index = Some(index);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tab_stop(mut self, tab_stop: bool) -> Self {
|
||||
self.tab_stop = tab_stop;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
|
||||
self.disabled = disabled;
|
||||
self.editor
|
||||
@@ -151,6 +167,16 @@ impl Render for InputField {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
|
||||
let configured_handle = if let Some(tab_index) = self.tab_index {
|
||||
focus_handle.tab_index(tab_index).tab_stop(self.tab_stop)
|
||||
} else if !self.tab_stop {
|
||||
focus_handle.tab_stop(false)
|
||||
} else {
|
||||
focus_handle
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.id(self.placeholder.clone())
|
||||
.w_full()
|
||||
@@ -168,6 +194,7 @@ impl Render for InputField {
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.track_focus(&configured_handle)
|
||||
.min_w(self.min_width)
|
||||
.min_h_8()
|
||||
.w_full()
|
||||
|
||||
@@ -4702,6 +4702,7 @@ mod tests {
|
||||
"assistant",
|
||||
"assistant2",
|
||||
"auto_update",
|
||||
"bedrock",
|
||||
"branches",
|
||||
"buffer_search",
|
||||
"channel_modal",
|
||||
|
||||
Reference in New Issue
Block a user