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