assistant_configuration.rs

  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}