1use std::sync::Arc;
2
3use client::{Client, UserStore};
4use gpui::{Action, ClickEvent, Entity, IntoElement, ParentElement};
5use language_model::{LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
6use ui::{Divider, List, prelude::*};
7use zed_actions::agent::{OpenConfiguration, ToggleModelSelector};
8
9use crate::{AgentPanelOnboardingCard, BulletItem, ZedAiOnboarding};
10
11pub struct AgentPanelOnboarding {
12 user_store: Entity<UserStore>,
13 client: Arc<Client>,
14 configured_providers: Vec<(IconName, SharedString)>,
15 continue_with_zed_ai: Arc<dyn Fn(&mut Window, &mut App)>,
16}
17
18impl AgentPanelOnboarding {
19 pub fn new(
20 user_store: Entity<UserStore>,
21 client: Arc<Client>,
22 continue_with_zed_ai: impl Fn(&mut Window, &mut App) + 'static,
23 cx: &mut Context<Self>,
24 ) -> Self {
25 cx.subscribe(
26 &LanguageModelRegistry::global(cx),
27 |this: &mut Self, _registry, event: &language_model::Event, cx| match event {
28 language_model::Event::ProviderStateChanged
29 | language_model::Event::AddedProvider(_)
30 | language_model::Event::RemovedProvider(_) => {
31 this.configured_providers = Self::compute_available_providers(cx)
32 }
33 _ => {}
34 },
35 )
36 .detach();
37
38 Self {
39 user_store,
40 client,
41 configured_providers: Self::compute_available_providers(cx),
42 continue_with_zed_ai: Arc::new(continue_with_zed_ai),
43 }
44 }
45
46 fn compute_available_providers(cx: &App) -> Vec<(IconName, SharedString)> {
47 LanguageModelRegistry::read_global(cx)
48 .providers()
49 .iter()
50 .filter(|provider| {
51 provider.is_authenticated(cx) && provider.id() != ZED_CLOUD_PROVIDER_ID
52 })
53 .map(|provider| (provider.icon(), provider.name().0.clone()))
54 .collect()
55 }
56
57 fn configure_providers(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
58 window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
59 cx.notify();
60 }
61
62 fn render_api_keys_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
63 let has_existing_providers = self.configured_providers.len() > 0;
64 let configure_provider_label = if has_existing_providers {
65 "Configure Other Provider"
66 } else {
67 "Configure Providers"
68 };
69
70 let content = if has_existing_providers {
71 List::new()
72 .child(BulletItem::new(
73 "Or start now using API keys from your environment for the following providers:"
74 ))
75 .child(
76 h_flex()
77 .px_5()
78 .gap_2()
79 .flex_wrap()
80 .children(self.configured_providers.iter().cloned().map(|(icon, name)|
81 h_flex()
82 .gap_1p5()
83 .child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
84 .child(Label::new(name))
85 ))
86 )
87 .child(BulletItem::new(
88 "No need for any of the plans or even to sign in",
89 ))
90 } else {
91 List::new()
92 .child(BulletItem::new(
93 "You can also use AI in Zed by bringing your own API keys",
94 ))
95 .child(BulletItem::new(
96 "No need for any of the plans or even to sign in",
97 ))
98 };
99
100 v_flex()
101 .mt_2()
102 .gap_1()
103 .child(
104 h_flex()
105 .gap_2()
106 .child(
107 Label::new("API Keys")
108 .size(LabelSize::Small)
109 .color(Color::Muted)
110 .buffer_font(cx),
111 )
112 .child(Divider::horizontal()),
113 )
114 .child(content)
115 .when(has_existing_providers, |this| {
116 this.child(
117 Button::new("pick-model", "Choose Model")
118 .full_width()
119 .style(ButtonStyle::Outlined)
120 .on_click(|_event, window, cx| {
121 window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
122 }),
123 )
124 })
125 .child(
126 Button::new("configure-providers", configure_provider_label)
127 .full_width()
128 .style(ButtonStyle::Outlined)
129 .on_click(cx.listener(Self::configure_providers)),
130 )
131 }
132}
133
134impl Render for AgentPanelOnboarding {
135 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
136 AgentPanelOnboardingCard::new()
137 .child(ZedAiOnboarding::new(
138 self.client.clone(),
139 &self.user_store,
140 self.continue_with_zed_ai.clone(),
141 cx,
142 ))
143 .child(self.render_api_keys_section(cx))
144 }
145}