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