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}