impl Focusable for Button

This commit is contained in:
Lukas Wirth 2025-08-13 16:40:11 +02:00
parent 6e540a58fa
commit e56672e542
107 changed files with 524 additions and 417 deletions

View file

@ -1017,13 +1017,14 @@ impl Render for ConfigurationView {
List::new()
.child(
InstructionListItem::new(
cx,
"Create one by visiting",
Some("Anthropic's settings"),
Some("https://console.anthropic.com/settings/keys")
)
)
.child(
InstructionListItem::text_only("Paste your API key below and hit enter to start using the assistant")
InstructionListItem::text_only(cx, "Paste your API key below and hit enter to start using the assistant")
)
)
.child(
@ -1066,7 +1067,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-key", "Reset Key")
Button::new("reset-key", "Reset Key", cx)
.label_size(LabelSize::Small)
.icon(Some(IconName::Trash))
.icon_size(IconSize::Small)

View file

@ -1232,7 +1232,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-key", "Reset Key")
Button::new("reset-key", "Reset Key", cx)
.icon(Some(IconName::Trash))
.icon_size(IconSize::Small)
.icon_position(IconPosition::Start)
@ -1257,6 +1257,7 @@ impl Render for ConfigurationView {
List::new()
.child(
InstructionListItem::new(
cx,
"Grant permissions to the strategy you'll use according to the:",
Some("Prerequisites"),
Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"),
@ -1264,6 +1265,7 @@ impl Render for ConfigurationView {
)
.child(
InstructionListItem::new(
cx,
"Select the models you would like access to:",
Some("Bedrock Model Catalog"),
Some("https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess"),
@ -1365,19 +1367,23 @@ impl ConfigurationView {
.child(
List::new()
.child(InstructionListItem::new(
cx,
"Create an IAM user in the AWS console with programmatic access",
Some("IAM Console"),
Some("https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users"),
))
.child(InstructionListItem::new(
cx,
"Attach the necessary Bedrock permissions to this ",
Some("user"),
Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"),
))
.child(InstructionListItem::text_only(
cx,
"Copy the access key ID and secret access key when provided",
))
.child(InstructionListItem::text_only(
cx,
"Enter these credentials below",
)),
)

View file

@ -410,12 +410,17 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
return None;
}
Some(
render_accept_terms(view, state.accept_terms_of_service_task.is_some(), {
let state = self.state.clone();
move |_window, cx| {
state.update(cx, |state, cx| state.accept_terms_of_service(cx));
}
})
render_accept_terms(
view,
state.accept_terms_of_service_task.is_some(),
{
let state = self.state.clone();
move |_window, cx| {
state.update(cx, |state, cx| state.accept_terms_of_service(cx));
}
},
cx,
)
.into_any_element(),
)
}
@ -429,11 +434,12 @@ fn render_accept_terms(
view_kind: LanguageModelProviderTosView,
accept_terms_of_service_in_progress: bool,
accept_terms_callback: impl Fn(&mut Window, &mut App) + 'static,
app: &App,
) -> impl IntoElement {
let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart);
let thread_empty_state = matches!(view_kind, LanguageModelProviderTosView::ThreadEmptyState);
let terms_button = Button::new("terms_of_service", "Terms of Service")
let terms_button = Button::new("terms_of_service", "Terms of Service", app)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
.icon_color(Color::Muted)
@ -442,7 +448,7 @@ fn render_accept_terms(
.on_click(move |_, _window, cx| cx.open_url("https://zed.dev/terms-of-service"));
let button_container = h_flex().child(
Button::new("accept_terms", "I accept the Terms of Service")
Button::new("accept_terms", "I accept the Terms of Service", app)
.when(!thread_empty_state, |this| {
this.full_width()
.style(ButtonStyle::Tinted(TintColor::Accent))
@ -1135,19 +1141,19 @@ impl RenderOnce for ZedAiConfiguration {
};
let manage_subscription_buttons = if is_pro {
Button::new("manage_settings", "Manage Subscription")
Button::new("manage_settings", "Manage Subscription", _cx)
.full_width()
.style(ButtonStyle::Tinted(TintColor::Accent))
.on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx)))
.into_any_element()
} else if self.plan.is_none() || self.eligible_for_trial {
Button::new("start_trial", "Start 14-day Free Pro Trial")
Button::new("start_trial", "Start 14-day Free Pro Trial", _cx)
.full_width()
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(|_, _, cx| cx.open_url(&zed_urls::start_trial_url(cx)))
.into_any_element()
} else {
Button::new("upgrade", "Upgrade to Pro")
Button::new("upgrade", "Upgrade to Pro", _cx)
.full_width()
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx)))
@ -1159,7 +1165,7 @@ impl RenderOnce for ZedAiConfiguration {
.gap_2()
.child(Label::new("Sign in to have access to Zed's complete agentic experience with hosted models."))
.child(
Button::new("sign_in", "Sign In to use Zed AI")
Button::new("sign_in", "Sign In to use Zed AI", _cx)
.icon_color(Color::Muted)
.icon(IconName::Github)
.icon_size(IconSize::Small)
@ -1183,12 +1189,13 @@ impl RenderOnce for ZedAiConfiguration {
let callback = self.accept_terms_of_service_callback.clone();
move |window, cx| (callback)(window, cx)
},
_cx,
))
})
.map(|this| {
if self.has_accepted_terms_of_service && self.account_too_young {
this.child(young_account_banner).child(
Button::new("upgrade", "Upgrade to Pro")
Button::new("upgrade", "Upgrade to Pro", _cx)
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.full_width()
.on_click(|_, _, cx| {

View file

@ -670,7 +670,7 @@ impl Render for ConfigurationView {
.child(Label::new("Authorized")),
)
.child(
Button::new("sign_out", "Sign Out")
Button::new("sign_out", "Sign Out", cx)
.label_size(LabelSize::Small)
.on_click(|_, window, cx| {
window.dispatch_action(copilot::SignOut.boxed_clone(), cx);
@ -709,7 +709,7 @@ impl Render for ConfigurationView {
const LABEL: &str = "To use Zed's agent with GitHub Copilot, you need to be logged in to GitHub. Note that your GitHub account must have an active Copilot Chat subscription.";
v_flex().gap_2().child(Label::new(LABEL)).child(
Button::new("sign_in", "Sign in to use GitHub Copilot")
Button::new("sign_in", "Sign in to use GitHub Copilot", cx)
.icon_color(Color::Muted)
.icon(IconName::Github)
.icon_position(IconPosition::Start)

View file

@ -679,11 +679,13 @@ impl Render for ConfigurationView {
.child(
List::new()
.child(InstructionListItem::new(
cx,
"Get your API key from the",
Some("DeepSeek console"),
Some("https://platform.deepseek.com/api_keys"),
))
.child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant",
)),
)
@ -728,7 +730,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-key", "Reset Key")
Button::new("reset-key", "Reset Key", cx)
.label_size(LabelSize::Small)
.icon(Some(IconName::Trash))
.icon_size(IconSize::Small)

View file

@ -884,11 +884,13 @@ impl Render for ConfigurationView {
.child(
List::new()
.child(InstructionListItem::new(
cx,
"Create one by visiting",
Some("Google AI's console"),
Some("https://aistudio.google.com/app/apikey"),
))
.child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant",
)),
)
@ -931,7 +933,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-key", "Reset Key")
Button::new("reset-key", "Reset Key", cx)
.label_size(LabelSize::Small)
.icon(Some(IconName::Trash))
.icon_size(IconSize::Small)

View file

@ -668,9 +668,11 @@ impl Render for ConfigurationView {
v_flex().gap_1().child(Label::new(lmstudio_intro)).child(
List::new()
.child(InstructionListItem::text_only(
cx,
"LM Studio needs to be running with at least one model downloaded.",
))
.child(InstructionListItem::text_only(
cx,
"To get your first model, try running `lms get qwen2.5-coder-7b`",
)),
),
@ -687,7 +689,7 @@ impl Render for ConfigurationView {
.map(|this| {
if is_authenticated {
this.child(
Button::new("lmstudio-site", "LM Studio")
Button::new("lmstudio-site", "LM Studio", cx)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small)
@ -702,6 +704,7 @@ impl Render for ConfigurationView {
Button::new(
"download_lmstudio_button",
"Download LM Studio",
cx,
)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
@ -715,7 +718,7 @@ impl Render for ConfigurationView {
}
})
.child(
Button::new("view-models", "Model Catalog")
Button::new("view-models", "Model Catalog", cx)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small)
@ -741,7 +744,7 @@ impl Render for ConfigurationView {
)
} else {
this.child(
Button::new("retry_lmstudio_models", "Connect")
Button::new("retry_lmstudio_models", "Connect", cx)
.icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall)
.icon(IconName::PlayFilled)

View file

@ -848,14 +848,17 @@ impl Render for ConfigurationView {
.child(
List::new()
.child(InstructionListItem::new(
cx,
"Create one by visiting",
Some("Mistral's console"),
Some("https://console.mistral.ai/api-keys"),
))
.child(InstructionListItem::text_only(
cx,
"Ensure your Mistral account has credits",
))
.child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant",
)),
)
@ -898,7 +901,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-key", "Reset Key")
Button::new("reset-key", "Reset Key", cx)
.label_size(LabelSize::Small)
.icon(Some(IconName::Trash))
.icon_size(IconSize::Small)

View file

@ -587,8 +587,9 @@ impl Render for ConfigurationView {
.child(
v_flex().gap_1().child(Label::new(ollama_intro)).child(
List::new()
.child(InstructionListItem::text_only("Ollama must be running with at least one model installed to use it in the assistant."))
.child(InstructionListItem::text_only(cx, "Ollama must be running with at least one model installed to use it in the assistant."))
.child(InstructionListItem::text_only(
cx,
"Once installed, try `ollama run llama3.2`",
)),
),
@ -605,7 +606,7 @@ impl Render for ConfigurationView {
.map(|this| {
if is_authenticated {
this.child(
Button::new("ollama-site", "Ollama")
Button::new("ollama-site", "Ollama", cx)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small)
@ -618,6 +619,7 @@ impl Render for ConfigurationView {
Button::new(
"download_ollama_button",
"Download Ollama",
cx,
)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
@ -631,7 +633,7 @@ impl Render for ConfigurationView {
}
})
.child(
Button::new("view-models", "View All Models")
Button::new("view-models", "View All Models", cx)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small)
@ -655,7 +657,7 @@ impl Render for ConfigurationView {
)
} else {
this.child(
Button::new("retry_ollama_models", "Connect")
Button::new("retry_ollama_models", "Connect", cx)
.icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall)
.icon(IconName::PlayFilled)

View file

@ -812,14 +812,17 @@ impl Render for ConfigurationView {
.child(
List::new()
.child(InstructionListItem::new(
cx,
"Create one by visiting",
Some("OpenAI's console"),
Some("https://platform.openai.com/api-keys"),
))
.child(InstructionListItem::text_only(
cx,
"Ensure your OpenAI account has credits",
))
.child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant",
)),
)
@ -857,7 +860,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-api-key", "Reset API Key")
Button::new("reset-api-key", "Reset API Key",cx)
.label_size(LabelSize::Small)
.icon(IconName::Undo)
.icon_size(IconSize::Small)
@ -891,7 +894,7 @@ impl Render for ConfigurationView {
.child(Label::new("Zed also supports OpenAI-compatible models.")),
)
.child(
Button::new("docs", "Learn More")
Button::new("docs", "Learn More", cx)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)

View file

@ -505,7 +505,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-api-key", "Reset API Key")
Button::new("reset-api-key", "Reset API Key",cx)
.label_size(LabelSize::Small)
.icon(IconName::Undo)
.icon_size(IconSize::Small)

View file

@ -859,14 +859,17 @@ impl Render for ConfigurationView {
.child(
List::new()
.child(InstructionListItem::new(
cx,
"Create an API key by visiting",
Some("OpenRouter's console"),
Some("https://openrouter.ai/keys"),
))
.child(InstructionListItem::text_only(
cx,
"Ensure your OpenRouter account has credits",
))
.child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant",
)),
)
@ -909,7 +912,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-key", "Reset Key")
Button::new("reset-key", "Reset Key",cx)
.label_size(LabelSize::Small)
.icon(Some(IconName::Trash))
.icon_size(IconSize::Small)

View file

@ -517,11 +517,13 @@ impl Render for ConfigurationView {
.child(
List::new()
.child(InstructionListItem::new(
cx,
"Create one by visiting",
Some("Vercel v0's console"),
Some("https://v0.dev/chat/settings/keys"),
))
.child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the agent",
)),
)
@ -559,7 +561,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-api-key", "Reset API Key")
Button::new("reset-api-key", "Reset API Key",cx)
.label_size(LabelSize::Small)
.icon(IconName::Undo)
.icon_size(IconSize::Small)

View file

@ -507,11 +507,13 @@ impl Render for ConfigurationView {
.child(
List::new()
.child(InstructionListItem::new(
cx,
"Create one by visiting",
Some("xAI console"),
Some("https://console.x.ai/team/default/api-keys"),
))
.child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the agent",
)),
)
@ -549,7 +551,7 @@ impl Render for ConfigurationView {
})),
)
.child(
Button::new("reset-api-key", "Reset API Key")
Button::new("reset-api-key", "Reset API Key",cx)
.label_size(LabelSize::Small)
.icon(IconName::Undo)
.icon_size(IconSize::Small)

View file

@ -2,27 +2,31 @@ use gpui::{AnyElement, IntoElement, ParentElement, SharedString};
use ui::{ListItem, prelude::*};
/// A reusable list item component for adding LLM provider configuration instructions
pub struct InstructionListItem {
pub struct InstructionListItem<'app> {
app: &'app App,
label: SharedString,
button_label: Option<SharedString>,
button_link: Option<String>,
}
impl InstructionListItem {
impl<'app> InstructionListItem<'app> {
pub fn new(
app: &'app App,
label: impl Into<SharedString>,
button_label: Option<impl Into<SharedString>>,
button_link: Option<impl Into<String>>,
) -> Self {
Self {
app,
label: label.into(),
button_label: button_label.map(|l| l.into()),
button_link: button_link.map(|l| l.into()),
}
}
pub fn text_only(label: impl Into<SharedString>) -> Self {
pub fn text_only(app: &'app App, label: impl Into<SharedString>) -> Self {
Self {
app,
label: label.into(),
button_label: None,
button_link: None,
@ -30,7 +34,7 @@ impl InstructionListItem {
}
}
impl IntoElement for InstructionListItem {
impl IntoElement for InstructionListItem<'_> {
type Element = AnyElement;
fn into_element(self) -> Self::Element {
@ -44,7 +48,7 @@ impl IntoElement for InstructionListItem {
.flex_wrap()
.child(Label::new(self.label))
.child(
Button::new(unique_id, button_label)
Button::new(unique_id, button_label, self.app)
.style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small)