Detailed changes
@@ -8,6 +8,7 @@ use provider::deepseek::DeepSeekLanguageModelProvider;
pub mod provider;
mod settings;
+pub mod ui;
use crate::provider::anthropic::AnthropicLanguageModelProvider;
use crate::provider::bedrock::BedrockLanguageModelProvider;
@@ -1,3 +1,4 @@
+use crate::ui::InstructionListItem;
use crate::AllLanguageModelSettings;
use anthropic::{AnthropicError, ContentDelta, Event, ResponseContent};
use anyhow::{anyhow, Context as _, Result};
@@ -24,7 +25,7 @@ use std::str::FromStr;
use std::sync::Arc;
use strum::IntoEnumIterator;
use theme::ThemeSettings;
-use ui::{prelude::*, Icon, IconName, Tooltip};
+use ui::{prelude::*, Icon, IconName, List, Tooltip};
use util::{maybe, ResultExt};
const PROVIDER_ID: &str = language_model::ANTHROPIC_PROVIDER_ID;
@@ -803,12 +804,6 @@ impl ConfigurationView {
impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const ANTHROPIC_CONSOLE_URL: &str = "https://console.anthropic.com/settings/keys";
- const INSTRUCTIONS: [&str; 3] = [
- "To use Zed's assistant with Anthropic, you need to add an API key. Follow these steps:",
- "- Create one at:",
- "- Paste your API key below and hit enter to use the assistant:",
- ];
let env_var_set = self.state.read(cx).api_key_from_env;
if self.load_credentials_task.is_some() {
@@ -817,17 +812,20 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.on_action(cx.listener(Self::save_api_key))
- .child(Label::new(INSTRUCTIONS[0]))
- .child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
- Button::new("anthropic_console", ANTHROPIC_CONSOLE_URL)
- .style(ButtonStyle::Subtle)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(move |_, _, cx| cx.open_url(ANTHROPIC_CONSOLE_URL))
- )
+ .child(Label::new("To use Zed's assistant with Anthropic, you need to add an API key. Follow these steps:"))
+ .child(
+ List::new()
+ .child(
+ InstructionListItem::new(
+ "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")
+ )
)
- .child(Label::new(INSTRUCTIONS[2]))
.child(
h_flex()
.w_full()
@@ -844,7 +842,8 @@ impl Render for ConfigurationView {
Label::new(
format!("You can also assign the {ANTHROPIC_API_KEY_VAR} environment variable and restart Zed."),
)
- .size(LabelSize::Small),
+ .size(LabelSize::Small)
+ .color(Color::Muted),
)
.into_any()
} else {
@@ -2,6 +2,7 @@ use std::pin::Pin;
use std::str::FromStr;
use std::sync::Arc;
+use crate::ui::InstructionListItem;
use anyhow::{anyhow, Context as _, Result};
use aws_config::stalled_stream_protection::StalledStreamProtectionConfig;
use aws_config::Region;
@@ -37,7 +38,7 @@ use settings::{Settings, SettingsStore};
use strum::IntoEnumIterator;
use theme::ThemeSettings;
use tokio::runtime::Handle;
-use ui::{prelude::*, Icon, IconName, Tooltip};
+use ui::{prelude::*, Icon, IconName, List, Tooltip};
use util::{maybe, ResultExt};
use crate::AllLanguageModelSettings;
@@ -954,23 +955,7 @@ impl ConfigurationView {
impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const IAM_CONSOLE_URL: &str = "https://us-east-1.console.aws.amazon.com/iam/home";
- const BEDROCK_DOCS_URL: &str =
- "https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html";
- const BEDROCK_MODEL_CATALOG: &str =
- "https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess";
- const INSTRUCTIONS: [&str; 5] = [
- "To use Zed's assistant with Bedrock, you need to add the Access Key ID, Secret Access Key and AWS Region. Follow these steps:",
- "- Create a user and security credentials here:",
- "- Grant that user permissions according to this documentation:",
- "- Go to the console and select the models you would like access to:",
- "- Fill the fields below and hit enter to use the assistant:",
- ];
- const BEDROCK_MODEL_CATALOG_LABEL: &str = "Bedrock Model Catalog";
- const BEDROCK_IAM_DOCS: &str = "Prerequisites";
-
let env_var_set = self.state.read(cx).credentials_from_env;
-
let bg_color = cx.theme().colors().editor_background;
let border_color = cx.theme().colors().border_variant;
let input_base_styles = || {
@@ -990,33 +975,34 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.on_action(cx.listener(ConfigurationView::save_credentials))
- .child(Label::new(INSTRUCTIONS[0]))
- .child(h_flex().flex_wrap().child(Label::new(INSTRUCTIONS[1])).child(
- Button::new("iam_console", IAM_CONSOLE_URL)
- .style(ButtonStyle::Subtle)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(move |_, _window, cx| cx.open_url(IAM_CONSOLE_URL))
- )
+ .child(Label::new("To use Zed's assistant with Bedrock, you need to add the Access Key ID, Secret Access Key and AWS Region. Follow these steps:"))
+ .child(
+ List::new()
+ .child(
+ InstructionListItem::new(
+ "Start by",
+ Some("creating a user and security credentials"),
+ Some("https://us-east-1.console.aws.amazon.com/iam/home")
+ )
+ )
+ .child(
+ InstructionListItem::new(
+ "Grant that user permissions according to this documentation:",
+ Some("Prerequisites"),
+ Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html")
+ )
+ )
+ .child(
+ InstructionListItem::new(
+ "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")
+ )
+ )
+ .child(
+ InstructionListItem::text_only("Fill the fields below and hit enter to start using the assistant")
+ )
)
- .child(h_flex().flex_wrap().child(Label::new(INSTRUCTIONS[2])).child(
- Button::new("bedrock_iam_docs", BEDROCK_IAM_DOCS)
- .style(ButtonStyle::Subtle)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(move |_, _window, cx| cx.open_url(BEDROCK_DOCS_URL))
- ))
- .child(h_flex().flex_wrap().child(Label::new(INSTRUCTIONS[3])).child(
- Button::new("bedrock_model_catalog", BEDROCK_MODEL_CATALOG_LABEL)
- .style(ButtonStyle::Subtle)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(move |_, _window, cx| cx.open_url(BEDROCK_MODEL_CATALOG))
- ))
- .child(Label::new(INSTRUCTIONS[4]))
.child(
v_flex()
.my_2()
@@ -1050,7 +1036,8 @@ impl Render for ConfigurationView {
Label::new(
format!("You can also assign the {ZED_BEDROCK_ACCESS_KEY_ID_VAR}, {ZED_BEDROCK_SECRET_ACCESS_KEY_VAR}, and {ZED_BEDROCK_REGION_VAR} environment variables and restart Zed."),
)
- .size(LabelSize::Small),
+ .size(LabelSize::Small)
+ .color(Color::Muted),
)
.into_any()
} else {
@@ -18,10 +18,10 @@ use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use std::sync::Arc;
use theme::ThemeSettings;
-use ui::{prelude::*, Icon, IconName};
+use ui::{prelude::*, Icon, IconName, List};
use util::ResultExt;
-use crate::AllLanguageModelSettings;
+use crate::{ui::InstructionListItem, AllLanguageModelSettings};
const PROVIDER_ID: &str = "deepseek";
const PROVIDER_NAME: &str = "DeepSeek";
@@ -607,13 +607,6 @@ impl ConfigurationView {
impl Render for ConfigurationView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const DEEPSEEK_CONSOLE_URL: &str = "https://platform.deepseek.com/api_keys";
- const INSTRUCTIONS: [&str; 3] = [
- "To use DeepSeek in Zed, you need an API key:",
- "- Get your API key from:",
- "- Paste it below and press enter:",
- ];
-
let env_var_set = self.state.read(cx).api_key_from_env;
if self.load_credentials_task.is_some() {
@@ -622,18 +615,18 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.on_action(cx.listener(Self::save_api_key))
- .child(Label::new(INSTRUCTIONS[0]))
+ .child(Label::new("To use DeepSeek in Zed, you need an API key:"))
.child(
- h_flex().child(Label::new(INSTRUCTIONS[1])).child(
- Button::new("deepseek_console", DEEPSEEK_CONSOLE_URL)
- .style(ButtonStyle::Subtle)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(move |_, _window, cx| cx.open_url(DEEPSEEK_CONSOLE_URL)),
- ),
+ List::new()
+ .child(InstructionListItem::new(
+ "Get your API key from the",
+ Some("DeepSeek console"),
+ Some("https://platform.deepseek.com/api_keys"),
+ ))
+ .child(InstructionListItem::text_only(
+ "Paste your API key below and hit enter to start using the assistant",
+ )),
)
- .child(Label::new(INSTRUCTIONS[2]))
.child(
h_flex()
.w_full()
@@ -651,7 +644,8 @@ impl Render for ConfigurationView {
"Or set the {} environment variable.",
DEEPSEEK_API_KEY_VAR
))
- .size(LabelSize::Small),
+ .size(LabelSize::Small)
+ .color(Color::Muted),
)
.into_any()
} else {
@@ -20,9 +20,10 @@ use settings::{Settings, SettingsStore};
use std::{future, sync::Arc};
use strum::IntoEnumIterator;
use theme::ThemeSettings;
-use ui::{prelude::*, Icon, IconName, Tooltip};
+use ui::{prelude::*, Icon, IconName, List, Tooltip};
use util::ResultExt;
+use crate::ui::InstructionListItem;
use crate::AllLanguageModelSettings;
const PROVIDER_ID: &str = "google";
@@ -508,13 +509,6 @@ impl ConfigurationView {
impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const GOOGLE_CONSOLE_URL: &str = "https://aistudio.google.com/app/apikey";
- const INSTRUCTIONS: [&str; 3] = [
- "To use Zed's assistant with Google AI, you need to add an API key. Follow these steps:",
- "- Create one by visiting:",
- "- Paste your API key below and hit enter to use the assistant",
- ];
-
let env_var_set = self.state.read(cx).api_key_from_env;
if self.load_credentials_task.is_some() {
@@ -523,17 +517,18 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.on_action(cx.listener(Self::save_api_key))
- .child(Label::new(INSTRUCTIONS[0]))
- .child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
- Button::new("google_console", GOOGLE_CONSOLE_URL)
- .style(ButtonStyle::Subtle)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(move |_, _, cx| cx.open_url(GOOGLE_CONSOLE_URL))
- )
+ .child(Label::new("To use Zed's assistant with Google AI, you need to add an API key. Follow these steps:"))
+ .child(
+ List::new()
+ .child(InstructionListItem::new(
+ "Create one by visiting",
+ Some("Google AI's console"),
+ Some("https://aistudio.google.com/app/apikey"),
+ ))
+ .child(InstructionListItem::text_only(
+ "Paste your API key below and hit enter to start using the assistant",
+ )),
)
- .child(Label::new(INSTRUCTIONS[2]))
.child(
h_flex()
.w_full()
@@ -550,7 +545,7 @@ impl Render for ConfigurationView {
Label::new(
format!("You can also assign the {GOOGLE_AI_API_KEY_VAR} environment variable and restart Zed."),
)
- .size(LabelSize::Small),
+ .size(LabelSize::Small).color(Color::Muted),
)
.into_any()
} else {
@@ -20,10 +20,10 @@ use settings::{Settings, SettingsStore};
use std::sync::Arc;
use strum::IntoEnumIterator;
use theme::ThemeSettings;
-use ui::{prelude::*, Icon, IconName, Tooltip};
+use ui::{prelude::*, Icon, IconName, List, Tooltip};
use util::ResultExt;
-use crate::AllLanguageModelSettings;
+use crate::{ui::InstructionListItem, AllLanguageModelSettings};
const PROVIDER_ID: &str = "mistral";
const PROVIDER_NAME: &str = "Mistral";
@@ -570,14 +570,6 @@ impl ConfigurationView {
impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const MISTRAL_CONSOLE_URL: &str = "https://console.mistral.ai/api-keys";
- const INSTRUCTIONS: [&str; 4] = [
- "To use Zed's assistant with Mistral, you need to add an API key. Follow these steps:",
- " - Create one by visiting:",
- " - Ensure your Mistral account has credits",
- " - Paste your API key below and hit enter to start using the assistant",
- ];
-
let env_var_set = self.state.read(cx).api_key_from_env;
if self.load_credentials_task.is_some() {
@@ -586,19 +578,21 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.on_action(cx.listener(Self::save_api_key))
- .child(Label::new(INSTRUCTIONS[0]))
- .child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
- Button::new("mistral_console", MISTRAL_CONSOLE_URL)
- .style(ButtonStyle::Subtle)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(move |_, _, cx| cx.open_url(MISTRAL_CONSOLE_URL))
- )
+ .child(Label::new("To use Zed's assistant with Mistral, you need to add an API key. Follow these steps:"))
+ .child(
+ List::new()
+ .child(InstructionListItem::new(
+ "Create one by visiting",
+ Some("Mistral's console"),
+ Some("https://console.mistral.ai/api-keys"),
+ ))
+ .child(InstructionListItem::text_only(
+ "Ensure your Mistral account has credits",
+ ))
+ .child(InstructionListItem::text_only(
+ "Paste your API key below and hit enter to start using the assistant",
+ )),
)
- .children(
- (2..INSTRUCTIONS.len()).map(|n|
- Label::new(INSTRUCTIONS[n])).collect::<Vec<_>>())
.child(
h_flex()
.w_full()
@@ -615,7 +609,7 @@ impl Render for ConfigurationView {
Label::new(
format!("You can also assign the {MISTRAL_API_KEY_VAR} environment variable and restart Zed."),
)
- .size(LabelSize::Small),
+ .size(LabelSize::Small).color(Color::Muted),
)
.into_any()
} else {
@@ -21,10 +21,10 @@ use settings::{Settings, SettingsStore};
use std::sync::Arc;
use strum::IntoEnumIterator;
use theme::ThemeSettings;
-use ui::{prelude::*, Icon, IconName, Tooltip};
+use ui::{prelude::*, Icon, IconName, List, Tooltip};
use util::ResultExt;
-use crate::AllLanguageModelSettings;
+use crate::{ui::InstructionListItem, AllLanguageModelSettings};
const PROVIDER_ID: &str = "openai";
const PROVIDER_NAME: &str = "OpenAI";
@@ -540,14 +540,6 @@ impl ConfigurationView {
impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- const OPENAI_CONSOLE_URL: &str = "https://platform.openai.com/api-keys";
- const INSTRUCTIONS: [&str; 4] = [
- "To use Zed's assistant with OpenAI, you need to add an API key. Follow these steps:",
- " - Create one by visiting:",
- " - Ensure your OpenAI account has credits",
- " - Paste your API key below and hit enter to start using the assistant",
- ];
-
let env_var_set = self.state.read(cx).api_key_from_env;
if self.load_credentials_task.is_some() {
@@ -556,19 +548,21 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.on_action(cx.listener(Self::save_api_key))
- .child(Label::new(INSTRUCTIONS[0]))
- .child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
- Button::new("openai_console", OPENAI_CONSOLE_URL)
- .style(ButtonStyle::Subtle)
- .icon(IconName::ArrowUpRight)
- .icon_size(IconSize::XSmall)
- .icon_color(Color::Muted)
- .on_click(move |_, _, cx| cx.open_url(OPENAI_CONSOLE_URL))
- )
+ .child(Label::new("To use Zed's assistant with OpenAI, you need to add an API key. Follow these steps:"))
+ .child(
+ List::new()
+ .child(InstructionListItem::new(
+ "Create one by visiting",
+ Some("OpenAI's console"),
+ Some("https://platform.openai.com/api-keys"),
+ ))
+ .child(InstructionListItem::text_only(
+ "Ensure your OpenAI account has credits",
+ ))
+ .child(InstructionListItem::text_only(
+ "Paste your API key below and hit enter to start using the assistant",
+ )),
)
- .children(
- (2..INSTRUCTIONS.len()).map(|n|
- Label::new(INSTRUCTIONS[n])).collect::<Vec<_>>())
.child(
h_flex()
.w_full()
@@ -585,13 +579,13 @@ impl Render for ConfigurationView {
Label::new(
format!("You can also assign the {OPENAI_API_KEY_VAR} environment variable and restart Zed."),
)
- .size(LabelSize::Small),
+ .size(LabelSize::Small).color(Color::Muted),
)
.child(
Label::new(
"Note that having a subscription for another service like GitHub Copilot won't work.".to_string(),
)
- .size(LabelSize::Small),
+ .size(LabelSize::Small).color(Color::Muted),
)
.into_any()
} else {
@@ -0,0 +1,2 @@
+pub mod instruction_list_item;
+pub use instruction_list_item::InstructionListItem;
@@ -0,0 +1,66 @@
+use gpui::{AnyElement, IntoElement, ParentElement, SharedString};
+use ui::{prelude::*, ListItem};
+
+/// A reusable list item component for adding LLM provider configuration instructions
+pub struct InstructionListItem {
+ label: SharedString,
+ button_label: Option<SharedString>,
+ button_link: Option<String>,
+}
+
+impl InstructionListItem {
+ pub fn new(
+ label: impl Into<SharedString>,
+ button_label: Option<impl Into<SharedString>>,
+ button_link: Option<impl Into<String>>,
+ ) -> Self {
+ Self {
+ 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 {
+ Self {
+ label: label.into(),
+ button_label: None,
+ button_link: None,
+ }
+ }
+}
+
+impl IntoElement for InstructionListItem {
+ type Element = AnyElement;
+
+ fn into_element(self) -> Self::Element {
+ let item_content = if let (Some(button_label), Some(button_link)) =
+ (self.button_label, self.button_link)
+ {
+ let link = button_link.clone();
+ h_flex().flex_wrap().child(Label::new(self.label)).child(
+ Button::new("link-button", button_label)
+ .style(ButtonStyle::Subtle)
+ .icon(IconName::ArrowUpRight)
+ .icon_size(IconSize::XSmall)
+ .icon_color(Color::Muted)
+ .on_click(move |_, _window, cx| cx.open_url(&link)),
+ )
+ } else {
+ div().child(Label::new(self.label))
+ };
+
+ div()
+ .child(
+ ListItem::new("list-item")
+ .selectable(false)
+ .start_slot(
+ Icon::new(IconName::Dash)
+ .size(IconSize::XSmall)
+ .color(Color::Hidden),
+ )
+ .child(item_content),
+ )
+ .into_any()
+ }
+}