1use std::sync::Arc;
2
3use collections::HashMap;
4use gpui::{AnyView, App, EventEmitter, FocusHandle, Focusable, Subscription};
5use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
6use ui::{prelude::*, ElevationIndex};
7use zed_actions::assistant::DeployPromptLibrary;
8
9pub struct AssistantConfiguration {
10 focus_handle: FocusHandle,
11 configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
12 _registry_subscription: Subscription,
13}
14
15impl AssistantConfiguration {
16 pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
17 let focus_handle = cx.focus_handle();
18
19 let registry_subscription = cx.subscribe_in(
20 &LanguageModelRegistry::global(cx),
21 window,
22 |this, _, event: &language_model::Event, window, cx| match event {
23 language_model::Event::AddedProvider(provider_id) => {
24 let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
25 if let Some(provider) = provider {
26 this.add_provider_configuration_view(&provider, window, cx);
27 }
28 }
29 language_model::Event::RemovedProvider(provider_id) => {
30 this.remove_provider_configuration_view(provider_id);
31 }
32 _ => {}
33 },
34 );
35
36 let mut this = Self {
37 focus_handle,
38 configuration_views_by_provider: HashMap::default(),
39 _registry_subscription: registry_subscription,
40 };
41 this.build_provider_configuration_views(window, cx);
42 this
43 }
44
45 fn build_provider_configuration_views(&mut self, window: &mut Window, cx: &mut Context<Self>) {
46 let providers = LanguageModelRegistry::read_global(cx).providers();
47 for provider in providers {
48 self.add_provider_configuration_view(&provider, window, cx);
49 }
50 }
51
52 fn remove_provider_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
53 self.configuration_views_by_provider.remove(provider_id);
54 }
55
56 fn add_provider_configuration_view(
57 &mut self,
58 provider: &Arc<dyn LanguageModelProvider>,
59 window: &mut Window,
60 cx: &mut Context<Self>,
61 ) {
62 let configuration_view = provider.configuration_view(window, cx);
63 self.configuration_views_by_provider
64 .insert(provider.id(), configuration_view);
65 }
66}
67
68impl Focusable for AssistantConfiguration {
69 fn focus_handle(&self, _: &App) -> FocusHandle {
70 self.focus_handle.clone()
71 }
72}
73
74pub enum AssistantConfigurationEvent {
75 NewThread(Arc<dyn LanguageModelProvider>),
76}
77
78impl EventEmitter<AssistantConfigurationEvent> for AssistantConfiguration {}
79
80impl AssistantConfiguration {
81 fn render_provider_configuration(
82 &mut self,
83 provider: &Arc<dyn LanguageModelProvider>,
84 cx: &mut Context<Self>,
85 ) -> impl IntoElement {
86 let provider_id = provider.id().0.clone();
87 let provider_name = provider.name().0.clone();
88 let configuration_view = self
89 .configuration_views_by_provider
90 .get(&provider.id())
91 .cloned();
92
93 v_flex()
94 .gap_2()
95 .child(
96 h_flex()
97 .justify_between()
98 .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
99 .when(provider.is_authenticated(cx), |parent| {
100 parent.child(
101 h_flex().justify_end().child(
102 Button::new(
103 SharedString::from(format!("new-thread-{provider_id}")),
104 "Open New Thread",
105 )
106 .icon_position(IconPosition::Start)
107 .icon(IconName::Plus)
108 .style(ButtonStyle::Filled)
109 .layer(ElevationIndex::ModalSurface)
110 .on_click(cx.listener({
111 let provider = provider.clone();
112 move |_this, _event, _window, cx| {
113 cx.emit(AssistantConfigurationEvent::NewThread(
114 provider.clone(),
115 ))
116 }
117 })),
118 ),
119 )
120 }),
121 )
122 .child(
123 div()
124 .p(DynamicSpacing::Base08.rems(cx))
125 .bg(cx.theme().colors().surface_background)
126 .border_1()
127 .border_color(cx.theme().colors().border_variant)
128 .rounded_md()
129 .map(|parent| match configuration_view {
130 Some(configuration_view) => parent.child(configuration_view),
131 None => parent.child(div().child(Label::new(format!(
132 "No configuration view for {provider_name}",
133 )))),
134 }),
135 )
136 }
137}
138
139impl Render for AssistantConfiguration {
140 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
141 let providers = LanguageModelRegistry::read_global(cx).providers();
142
143 v_flex()
144 .id("assistant-configuration")
145 .track_focus(&self.focus_handle(cx))
146 .bg(cx.theme().colors().editor_background)
147 .size_full()
148 .overflow_y_scroll()
149 .child(
150 h_flex().p(DynamicSpacing::Base16.rems(cx)).child(
151 Button::new("open-prompt-library", "Open Prompt Library")
152 .style(ButtonStyle::Filled)
153 .full_width()
154 .icon(IconName::Book)
155 .icon_size(IconSize::Small)
156 .icon_position(IconPosition::Start)
157 .on_click(|_event, _window, cx| cx.dispatch_action(&DeployPromptLibrary)),
158 ),
159 )
160 .child(
161 v_flex()
162 .p(DynamicSpacing::Base16.rems(cx))
163 .mt_1()
164 .gap_6()
165 .flex_1()
166 .children(
167 providers
168 .into_iter()
169 .map(|provider| self.render_provider_configuration(&provider, cx)),
170 ),
171 )
172 }
173}