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}