diff --git a/crates/language_model/src/provider/anthropic.rs b/crates/language_model/src/provider/anthropic.rs index e2ed27fe67..479a4d97de 100644 --- a/crates/language_model/src/provider/anthropic.rs +++ b/crates/language_model/src/provider/anthropic.rs @@ -19,7 +19,7 @@ use settings::{Settings, SettingsStore}; use std::{sync::Arc, time::Duration}; use strum::IntoEnumIterator; use theme::ThemeSettings; -use ui::{prelude::*, Icon, IconName}; +use ui::{prelude::*, Icon, IconName, Tooltip}; use util::ResultExt; const PROVIDER_ID: &str = "anthropic"; @@ -54,8 +54,11 @@ pub struct AnthropicLanguageModelProvider { state: gpui::Model, } +const ANTHROPIC_API_KEY_VAR: &'static str = "ANTHROPIC_API_KEY"; + pub struct State { api_key: Option, + api_key_from_env: bool, _subscription: Subscription, } @@ -67,6 +70,7 @@ impl State { delete_credentials.await.ok(); this.update(&mut cx, |this, cx| { this.api_key = None; + this.api_key_from_env = false; cx.notify(); }) }) @@ -105,18 +109,20 @@ impl State { .clone(); cx.spawn(|this, mut cx| async move { - let api_key = if let Ok(api_key) = std::env::var("ANTHROPIC_API_KEY") { - api_key + let (api_key, from_env) = if let Ok(api_key) = std::env::var(ANTHROPIC_API_KEY_VAR) + { + (api_key, true) } else { let (_, api_key) = cx .update(|cx| cx.read_credentials(&api_url))? .await? .ok_or_else(|| anyhow!("credentials not found"))?; - String::from_utf8(api_key)? + (String::from_utf8(api_key)?, false) }; this.update(&mut cx, |this, cx| { this.api_key = Some(api_key); + this.api_key_from_env = from_env; cx.notify(); }) }) @@ -128,6 +134,7 @@ impl AnthropicLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut AppContext) -> Self { let state = cx.new_model(|cx| State { api_key: None, + api_key_from_env: false, _subscription: cx.observe_global::(|_, cx| { cx.notify(); }), @@ -537,6 +544,8 @@ impl Render for ConfigurationView { "Paste your Anthropic 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() { div().child(Label::new("Loading credentials...")).into_any() } else if self.should_render_editor(cx) { @@ -558,7 +567,7 @@ impl Render for ConfigurationView { ) .child( Label::new( - "You can also assign the ANTHROPIC_API_KEY environment variable and restart Zed.", + "You can also assign the {ANTHROPIC_API_KEY_VAR} environment variable and restart Zed.", ) .size(LabelSize::Small), ) @@ -571,13 +580,21 @@ impl Render for ConfigurationView { h_flex() .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) - .child(Label::new("API key configured.")), + .child(Label::new(if env_var_set { + format!("API key set in {ANTHROPIC_API_KEY_VAR} environment variable.") + } else { + "API key configured.".to_string() + })), ) .child( Button::new("reset-key", "Reset key") .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) + .disabled(env_var_set) + .when(env_var_set, |this| { + this.tooltip(|cx| Tooltip::text(format!("To reset your API key, unset the {ANTHROPIC_API_KEY_VAR} environment variable."), cx)) + }) .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), ) .into_any() diff --git a/crates/language_model/src/provider/google.rs b/crates/language_model/src/provider/google.rs index 96475f752f..2eaf92cd66 100644 --- a/crates/language_model/src/provider/google.rs +++ b/crates/language_model/src/provider/google.rs @@ -14,7 +14,7 @@ use settings::{Settings, SettingsStore}; use std::{future, sync::Arc, time::Duration}; use strum::IntoEnumIterator; use theme::ThemeSettings; -use ui::{prelude::*, Icon, IconName}; +use ui::{prelude::*, Icon, IconName, Tooltip}; use util::ResultExt; use crate::{ @@ -46,9 +46,12 @@ pub struct GoogleLanguageModelProvider { pub struct State { api_key: Option, + api_key_from_env: bool, _subscription: Subscription, } +const GOOGLE_AI_API_KEY_VAR: &'static str = "GOOGLE_AI_API_KEY"; + impl State { fn is_authenticated(&self) -> bool { self.api_key.is_some() @@ -61,6 +64,7 @@ impl State { delete_credentials.await.ok(); this.update(&mut cx, |this, cx| { this.api_key = None; + this.api_key_from_env = false; cx.notify(); }) }) @@ -90,18 +94,20 @@ impl State { .clone(); cx.spawn(|this, mut cx| async move { - let api_key = if let Ok(api_key) = std::env::var("GOOGLE_AI_API_KEY") { - api_key + let (api_key, from_env) = if let Ok(api_key) = std::env::var(GOOGLE_AI_API_KEY_VAR) + { + (api_key, true) } else { let (_, api_key) = cx .update(|cx| cx.read_credentials(&api_url))? .await? .ok_or_else(|| anyhow!("credentials not found"))?; - String::from_utf8(api_key)? + (String::from_utf8(api_key)?, false) }; this.update(&mut cx, |this, cx| { this.api_key = Some(api_key); + this.api_key_from_env = from_env; cx.notify(); }) }) @@ -113,6 +119,7 @@ impl GoogleLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut AppContext) -> Self { let state = cx.new_model(|cx| State { api_key: None, + api_key_from_env: false, _subscription: cx.observe_global::(|_, cx| { cx.notify(); }), @@ -422,6 +429,8 @@ impl Render for ConfigurationView { "Paste your Google AI 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() { div().child(Label::new("Loading credentials...")).into_any() } else if self.should_render_editor(cx) { @@ -443,7 +452,7 @@ impl Render for ConfigurationView { ) .child( Label::new( - "You can also assign the GOOGLE_AI_API_KEY environment variable and restart Zed.", + format!("You can also assign the {GOOGLE_AI_API_KEY_VAR} environment variable and restart Zed."), ) .size(LabelSize::Small), ) @@ -456,13 +465,21 @@ impl Render for ConfigurationView { h_flex() .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) - .child(Label::new("API key configured.")), + .child(Label::new(if env_var_set { + format!("API key set in {GOOGLE_AI_API_KEY_VAR} environment variable.") + } else { + "API key configured.".to_string() + })), ) .child( Button::new("reset-key", "Reset key") .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) + .disabled(env_var_set) + .when(env_var_set, |this| { + this.tooltip(|cx| Tooltip::text(format!("To reset your API key, unset the {GOOGLE_AI_API_KEY_VAR} environment variable."), cx)) + }) .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), ) .into_any() diff --git a/crates/language_model/src/provider/open_ai.rs b/crates/language_model/src/provider/open_ai.rs index 0d3ee56d74..65941f5c55 100644 --- a/crates/language_model/src/provider/open_ai.rs +++ b/crates/language_model/src/provider/open_ai.rs @@ -16,7 +16,7 @@ use settings::{Settings, SettingsStore}; use std::{sync::Arc, time::Duration}; use strum::IntoEnumIterator; use theme::ThemeSettings; -use ui::{prelude::*, Icon, IconName}; +use ui::{prelude::*, Icon, IconName, Tooltip}; use util::ResultExt; use crate::{ @@ -49,9 +49,12 @@ pub struct OpenAiLanguageModelProvider { pub struct State { api_key: Option, + api_key_from_env: bool, _subscription: Subscription, } +const OPENAI_API_KEY_VAR: &'static str = "OPENAI_API_KEY"; + impl State { fn is_authenticated(&self) -> bool { self.api_key.is_some() @@ -64,6 +67,7 @@ impl State { delete_credentials.await.log_err(); this.update(&mut cx, |this, cx| { this.api_key = None; + this.api_key_from_env = false; cx.notify(); }) }) @@ -92,17 +96,18 @@ impl State { .api_url .clone(); cx.spawn(|this, mut cx| async move { - let api_key = if let Ok(api_key) = std::env::var("OPENAI_API_KEY") { - api_key + let (api_key, from_env) = if let Ok(api_key) = std::env::var(OPENAI_API_KEY_VAR) { + (api_key, true) } else { let (_, api_key) = cx .update(|cx| cx.read_credentials(&api_url))? .await? .ok_or_else(|| anyhow!("credentials not found"))?; - String::from_utf8(api_key)? + (String::from_utf8(api_key)?, false) }; this.update(&mut cx, |this, cx| { this.api_key = Some(api_key); + this.api_key_from_env = from_env; cx.notify(); }) }) @@ -114,6 +119,7 @@ impl OpenAiLanguageModelProvider { pub fn new(http_client: Arc, cx: &mut AppContext) -> Self { let state = cx.new_model(|cx| State { api_key: None, + api_key_from_env: false, _subscription: cx.observe_global::(|_this: &mut State, cx| { cx.notify(); }), @@ -476,6 +482,8 @@ impl Render for ConfigurationView { "Paste your OpenAI 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() { div().child(Label::new("Loading credentials...")).into_any() } else if self.should_render_editor(cx) { @@ -497,7 +505,7 @@ impl Render for ConfigurationView { ) .child( Label::new( - "You can also assign the OPENAI_API_KEY environment variable and restart Zed.", + format!("You can also assign the {OPENAI_API_KEY_VAR} environment variable and restart Zed."), ) .size(LabelSize::Small), ) @@ -510,13 +518,21 @@ impl Render for ConfigurationView { h_flex() .gap_1() .child(Icon::new(IconName::Check).color(Color::Success)) - .child(Label::new("API key configured.")), + .child(Label::new(if env_var_set { + format!("API key set in {OPENAI_API_KEY_VAR} environment variable.") + } else { + "API key configured.".to_string() + })), ) .child( Button::new("reset-key", "Reset key") .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) + .disabled(env_var_set) + .when(env_var_set, |this| { + this.tooltip(|cx| Tooltip::text(format!("To reset your API key, unset the {OPENAI_API_KEY_VAR} environment variable."), cx)) + }) .on_click(cx.listener(|this, _, cx| this.reset_api_key(cx))), ) .into_any()