assistant panel: Show if env var with API key is set (#16453)

This makes it easier to debug why resetting a key doesn't work. We now
show when the key is set via an env var and if so, we disable the
reset-key button and instead give instructions.

![screenshot-2024-08-19-11 22
05@2x](https://github.com/user-attachments/assets/6c75dc82-cb61-4661-9647-f77fca8fdf41)


Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
Thorsten Ball 2024-08-19 11:34:58 +02:00 committed by GitHub
parent 14fa4abce4
commit f651333896
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 68 additions and 18 deletions

View File

@ -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<State>,
}
const ANTHROPIC_API_KEY_VAR: &'static str = "ANTHROPIC_API_KEY";
pub struct State {
api_key: Option<String>,
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<dyn HttpClient>, cx: &mut AppContext) -> Self {
let state = cx.new_model(|cx| State {
api_key: None,
api_key_from_env: false,
_subscription: cx.observe_global::<SettingsStore>(|_, 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()

View File

@ -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<String>,
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<dyn HttpClient>, cx: &mut AppContext) -> Self {
let state = cx.new_model(|cx| State {
api_key: None,
api_key_from_env: false,
_subscription: cx.observe_global::<SettingsStore>(|_, 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()

View File

@ -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<String>,
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<dyn HttpClient>, cx: &mut AppContext) -> Self {
let state = cx.new_model(|cx| State {
api_key: None,
api_key_from_env: false,
_subscription: cx.observe_global::<SettingsStore>(|_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()