agent_api_keys_onboarding.rs

  1use gpui::{Action, IntoElement, ParentElement, RenderOnce, point};
  2use language_model::{LanguageModelProvider, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
  3use ui::{Divider, List, ListBulletItem, prelude::*};
  4
  5#[derive(Clone)]
  6enum ProviderIcon {
  7    Name(IconName),
  8    Path(SharedString),
  9}
 10
 11impl ProviderIcon {
 12    fn from_provider(provider: &dyn LanguageModelProvider) -> Self {
 13        if let Some(path) = provider.icon_path() {
 14            Self::Path(path)
 15        } else {
 16            Self::Name(provider.icon())
 17        }
 18    }
 19}
 20
 21pub struct ApiKeysWithProviders {
 22    configured_providers: Vec<(ProviderIcon, SharedString)>,
 23}
 24
 25impl ApiKeysWithProviders {
 26    pub fn new(cx: &mut Context<Self>) -> Self {
 27        cx.subscribe(
 28            &LanguageModelRegistry::global(cx),
 29            |this: &mut Self, _registry, event: &language_model::Event, cx| match event {
 30                language_model::Event::ProviderStateChanged(_)
 31                | language_model::Event::AddedProvider(_)
 32                | language_model::Event::RemovedProvider(_)
 33                | language_model::Event::ProvidersChanged => {
 34                    this.configured_providers = Self::compute_configured_providers(cx)
 35                }
 36                _ => {}
 37            },
 38        )
 39        .detach();
 40
 41        Self {
 42            configured_providers: Self::compute_configured_providers(cx),
 43        }
 44    }
 45
 46    fn compute_configured_providers(cx: &App) -> Vec<(ProviderIcon, SharedString)> {
 47        LanguageModelRegistry::read_global(cx)
 48            .visible_providers()
 49            .iter()
 50            .filter(|provider| {
 51                provider.is_authenticated(cx) && provider.id() != ZED_CLOUD_PROVIDER_ID
 52            })
 53            .map(|provider| {
 54                (
 55                    ProviderIcon::from_provider(provider.as_ref()),
 56                    provider.name().0,
 57                )
 58            })
 59            .collect()
 60    }
 61}
 62
 63impl Render for ApiKeysWithProviders {
 64    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 65        let configured_providers_list =
 66            self.configured_providers
 67                .iter()
 68                .cloned()
 69                .map(|(icon, name)| {
 70                    h_flex()
 71                        .gap_1p5()
 72                        .child(
 73                            match icon {
 74                                ProviderIcon::Name(icon_name) => Icon::new(icon_name),
 75                                ProviderIcon::Path(icon_path) => Icon::from_external_svg(icon_path),
 76                            }
 77                            .size(IconSize::XSmall)
 78                            .color(Color::Muted),
 79                        )
 80                        .child(Label::new(name))
 81                });
 82        div()
 83            .mx_2p5()
 84            .p_1()
 85            .pb_0()
 86            .gap_2()
 87            .rounded_t_lg()
 88            .border_t_1()
 89            .border_x_1()
 90            .border_color(cx.theme().colors().border.opacity(0.5))
 91            .bg(cx.theme().colors().background.alpha(0.5))
 92            .shadow(vec![gpui::BoxShadow {
 93                color: gpui::black().opacity(0.15),
 94                offset: point(px(1.), px(-1.)),
 95                blur_radius: px(3.),
 96                spread_radius: px(0.),
 97            }])
 98            .child(
 99                h_flex()
100                    .px_2p5()
101                    .py_1p5()
102                    .gap_2()
103                    .flex_wrap()
104                    .rounded_t(px(5.))
105                    .overflow_hidden()
106                    .border_t_1()
107                    .border_x_1()
108                    .border_color(cx.theme().colors().border)
109                    .bg(cx.theme().colors().panel_background)
110                    .child(
111                        h_flex()
112                            .min_w_0()
113                            .gap_2()
114                            .child(
115                                Icon::new(IconName::Info)
116                                    .size(IconSize::XSmall)
117                                    .color(Color::Muted)
118                            )
119                            .child(
120                                div()
121                                    .w_full()
122                                    .child(
123                                        Label::new("Start now using API keys from your environment for the following providers:")
124                                            .color(Color::Muted)
125                                    )
126                            )
127                    )
128                    .children(configured_providers_list)
129            )
130    }
131}
132
133#[derive(IntoElement)]
134pub struct ApiKeysWithoutProviders;
135
136impl ApiKeysWithoutProviders {
137    pub fn new() -> Self {
138        Self
139    }
140}
141
142impl RenderOnce for ApiKeysWithoutProviders {
143    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
144        v_flex()
145            .mt_2()
146            .gap_1()
147            .child(
148                h_flex()
149                    .gap_2()
150                    .child(
151                        Label::new("API Keys")
152                            .size(LabelSize::Small)
153                            .color(Color::Muted)
154                            .buffer_font(cx),
155                    )
156                    .child(Divider::horizontal()),
157            )
158            .child(List::new().child(ListBulletItem::new(
159                "Add your own keys to use AI without signing in.",
160            )))
161            .child(
162                Button::new("configure-providers", "Configure Providers")
163                    .full_width()
164                    .style(ButtonStyle::Outlined)
165                    .on_click(move |_, window, cx| {
166                        window.dispatch_action(zed_actions::agent::OpenSettings.boxed_clone(), cx);
167                    }),
168            )
169    }
170}