assistant: Extract `ConfigurationView` to its own module (#23480)

Marshall Bowers created

This PR is a small refactoring that extracts the Assistant's
`ConfigurationView` into its own module.

Release Notes:

- N/A

Change summary

crates/assistant/src/assistant.rs               |   1 
crates/assistant/src/assistant_configuration.rs | 197 ++++++++++++++++++
crates/assistant/src/assistant_panel.rs         | 206 ------------------
3 files changed, 204 insertions(+), 200 deletions(-)

Detailed changes

crates/assistant/src/assistant.rs 🔗

@@ -1,5 +1,6 @@
 #![cfg_attr(target_os = "windows", allow(unused, dead_code))]
 
+mod assistant_configuration;
 pub mod assistant_panel;
 mod inline_assistant;
 pub mod slash_command_settings;

crates/assistant/src/assistant_configuration.rs 🔗

@@ -0,0 +1,197 @@
+use std::sync::Arc;
+
+use collections::HashMap;
+use gpui::{canvas, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView, Subscription};
+use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
+use ui::{prelude::*, ElevationIndex};
+use workspace::Item;
+
+pub struct ConfigurationView {
+    focus_handle: FocusHandle,
+    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
+    _registry_subscription: Subscription,
+}
+
+impl ConfigurationView {
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
+        let focus_handle = cx.focus_handle();
+
+        let registry_subscription = cx.subscribe(
+            &LanguageModelRegistry::global(cx),
+            |this, _, event: &language_model::Event, cx| match event {
+                language_model::Event::AddedProvider(provider_id) => {
+                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
+                    if let Some(provider) = provider {
+                        this.add_configuration_view(&provider, cx);
+                    }
+                }
+                language_model::Event::RemovedProvider(provider_id) => {
+                    this.remove_configuration_view(provider_id);
+                }
+                _ => {}
+            },
+        );
+
+        let mut this = Self {
+            focus_handle,
+            configuration_views: HashMap::default(),
+            _registry_subscription: registry_subscription,
+        };
+        this.build_configuration_views(cx);
+        this
+    }
+
+    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
+        let providers = LanguageModelRegistry::read_global(cx).providers();
+        for provider in providers {
+            self.add_configuration_view(&provider, cx);
+        }
+    }
+
+    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
+        self.configuration_views.remove(provider_id);
+    }
+
+    fn add_configuration_view(
+        &mut self,
+        provider: &Arc<dyn LanguageModelProvider>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        let configuration_view = provider.configuration_view(cx);
+        self.configuration_views
+            .insert(provider.id(), configuration_view);
+    }
+
+    fn render_provider_view(
+        &mut self,
+        provider: &Arc<dyn LanguageModelProvider>,
+        cx: &mut ViewContext<Self>,
+    ) -> Div {
+        let provider_id = provider.id().0.clone();
+        let provider_name = provider.name().0.clone();
+        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
+
+        let open_new_context = cx.listener({
+            let provider = provider.clone();
+            move |_, _, cx| {
+                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
+                    provider.clone(),
+                ))
+            }
+        });
+
+        v_flex()
+            .gap_2()
+            .child(
+                h_flex()
+                    .justify_between()
+                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
+                    .when(provider.is_authenticated(cx), move |this| {
+                        this.child(
+                            h_flex().justify_end().child(
+                                Button::new(
+                                    SharedString::from(format!("new-context-{provider_id}")),
+                                    "Open New Chat",
+                                )
+                                .icon_position(IconPosition::Start)
+                                .icon(IconName::Plus)
+                                .style(ButtonStyle::Filled)
+                                .layer(ElevationIndex::ModalSurface)
+                                .on_click(open_new_context),
+                            ),
+                        )
+                    }),
+            )
+            .child(
+                div()
+                    .p(DynamicSpacing::Base08.rems(cx))
+                    .bg(cx.theme().colors().surface_background)
+                    .border_1()
+                    .border_color(cx.theme().colors().border_variant)
+                    .rounded_md()
+                    .when(configuration_view.is_none(), |this| {
+                        this.child(div().child(Label::new(format!(
+                            "No configuration view for {}",
+                            provider_name
+                        ))))
+                    })
+                    .when_some(configuration_view, |this, configuration_view| {
+                        this.child(configuration_view)
+                    }),
+            )
+    }
+}
+
+impl Render for ConfigurationView {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let providers = LanguageModelRegistry::read_global(cx).providers();
+        let provider_views = providers
+            .into_iter()
+            .map(|provider| self.render_provider_view(&provider, cx))
+            .collect::<Vec<_>>();
+
+        let mut element = v_flex()
+            .id("assistant-configuration-view")
+            .track_focus(&self.focus_handle(cx))
+            .bg(cx.theme().colors().editor_background)
+            .size_full()
+            .overflow_y_scroll()
+            .child(
+                v_flex()
+                    .p(DynamicSpacing::Base16.rems(cx))
+                    .border_b_1()
+                    .border_color(cx.theme().colors().border)
+                    .gap_1()
+                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
+                    .child(
+                        Label::new(
+                            "At least one LLM provider must be configured to use the Assistant.",
+                        )
+                        .color(Color::Muted),
+                    ),
+            )
+            .child(
+                v_flex()
+                    .p(DynamicSpacing::Base16.rems(cx))
+                    .mt_1()
+                    .gap_6()
+                    .flex_1()
+                    .children(provider_views),
+            )
+            .into_any();
+
+        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
+        // because we couldn't the element to take up the size of the parent.
+        canvas(
+            move |bounds, cx| {
+                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
+                element
+            },
+            |_, mut element, cx| {
+                element.paint(cx);
+            },
+        )
+        .flex_1()
+        .w_full()
+    }
+}
+
+pub enum ConfigurationViewEvent {
+    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
+}
+
+impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
+
+impl FocusableView for ConfigurationView {
+    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
+    }
+}
+
+impl Item for ConfigurationView {
+    type Event = ConfigurationViewEvent;
+
+    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
+        Some("Configuration".into())
+    }
+}

crates/assistant/src/assistant_panel.rs 🔗

@@ -1,3 +1,4 @@
+use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
 use crate::{
     terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, DeployPromptLibrary,
     InlineAssistant, NewContext,
@@ -13,19 +14,15 @@ use assistant_settings::{AssistantDockPosition, AssistantSettings};
 use assistant_slash_command::SlashCommandWorkingSet;
 use assistant_tool::ToolWorkingSet;
 use client::{proto, Client, Status};
-use collections::HashMap;
 use editor::{Editor, EditorEvent};
 use fs::Fs;
 use gpui::{
-    canvas, div, prelude::*, Action, AnyView, AppContext, AsyncWindowContext, EventEmitter,
-    ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, Model,
-    ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription,
-    Task, UpdateGlobal, View, WeakView,
+    prelude::*, Action, AppContext, AsyncWindowContext, EventEmitter, ExternalPaths, FocusHandle,
+    FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render, Styled,
+    Subscription, Task, UpdateGlobal, View, WeakView,
 };
 use language::LanguageRegistry;
-use language_model::{
-    LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
-};
+use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
 use language_model_selector::LanguageModelSelector;
 use project::Project;
 use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
@@ -34,12 +31,11 @@ use settings::{update_settings_file, Settings};
 use smol::stream::StreamExt;
 use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
 use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
-use ui::{prelude::*, ContextMenu, ElevationIndex, PopoverMenu, PopoverMenuHandle, Tooltip};
+use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
 use util::{maybe, ResultExt};
 use workspace::DraggedTab;
 use workspace::{
     dock::{DockPosition, Panel, PanelEvent},
-    item::Item,
     pane, DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace,
 };
 use zed_actions::assistant::{InlineAssist, ToggleFocus};
@@ -1371,193 +1367,3 @@ pub enum WorkflowAssistStatus {
     Done,
     Idle,
 }
-
-pub struct ConfigurationView {
-    focus_handle: FocusHandle,
-    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
-    _registry_subscription: Subscription,
-}
-
-impl ConfigurationView {
-    fn new(cx: &mut ViewContext<Self>) -> Self {
-        let focus_handle = cx.focus_handle();
-
-        let registry_subscription = cx.subscribe(
-            &LanguageModelRegistry::global(cx),
-            |this, _, event: &language_model::Event, cx| match event {
-                language_model::Event::AddedProvider(provider_id) => {
-                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
-                    if let Some(provider) = provider {
-                        this.add_configuration_view(&provider, cx);
-                    }
-                }
-                language_model::Event::RemovedProvider(provider_id) => {
-                    this.remove_configuration_view(provider_id);
-                }
-                _ => {}
-            },
-        );
-
-        let mut this = Self {
-            focus_handle,
-            configuration_views: HashMap::default(),
-            _registry_subscription: registry_subscription,
-        };
-        this.build_configuration_views(cx);
-        this
-    }
-
-    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
-        let providers = LanguageModelRegistry::read_global(cx).providers();
-        for provider in providers {
-            self.add_configuration_view(&provider, cx);
-        }
-    }
-
-    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
-        self.configuration_views.remove(provider_id);
-    }
-
-    fn add_configuration_view(
-        &mut self,
-        provider: &Arc<dyn LanguageModelProvider>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let configuration_view = provider.configuration_view(cx);
-        self.configuration_views
-            .insert(provider.id(), configuration_view);
-    }
-
-    fn render_provider_view(
-        &mut self,
-        provider: &Arc<dyn LanguageModelProvider>,
-        cx: &mut ViewContext<Self>,
-    ) -> Div {
-        let provider_id = provider.id().0.clone();
-        let provider_name = provider.name().0.clone();
-        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
-
-        let open_new_context = cx.listener({
-            let provider = provider.clone();
-            move |_, _, cx| {
-                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
-                    provider.clone(),
-                ))
-            }
-        });
-
-        v_flex()
-            .gap_2()
-            .child(
-                h_flex()
-                    .justify_between()
-                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
-                    .when(provider.is_authenticated(cx), move |this| {
-                        this.child(
-                            h_flex().justify_end().child(
-                                Button::new(
-                                    SharedString::from(format!("new-context-{provider_id}")),
-                                    "Open New Chat",
-                                )
-                                .icon_position(IconPosition::Start)
-                                .icon(IconName::Plus)
-                                .style(ButtonStyle::Filled)
-                                .layer(ElevationIndex::ModalSurface)
-                                .on_click(open_new_context),
-                            ),
-                        )
-                    }),
-            )
-            .child(
-                div()
-                    .p(DynamicSpacing::Base08.rems(cx))
-                    .bg(cx.theme().colors().surface_background)
-                    .border_1()
-                    .border_color(cx.theme().colors().border_variant)
-                    .rounded_md()
-                    .when(configuration_view.is_none(), |this| {
-                        this.child(div().child(Label::new(format!(
-                            "No configuration view for {}",
-                            provider_name
-                        ))))
-                    })
-                    .when_some(configuration_view, |this, configuration_view| {
-                        this.child(configuration_view)
-                    }),
-            )
-    }
-}
-
-impl Render for ConfigurationView {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let providers = LanguageModelRegistry::read_global(cx).providers();
-        let provider_views = providers
-            .into_iter()
-            .map(|provider| self.render_provider_view(&provider, cx))
-            .collect::<Vec<_>>();
-
-        let mut element = v_flex()
-            .id("assistant-configuration-view")
-            .track_focus(&self.focus_handle(cx))
-            .bg(cx.theme().colors().editor_background)
-            .size_full()
-            .overflow_y_scroll()
-            .child(
-                v_flex()
-                    .p(DynamicSpacing::Base16.rems(cx))
-                    .border_b_1()
-                    .border_color(cx.theme().colors().border)
-                    .gap_1()
-                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
-                    .child(
-                        Label::new(
-                            "At least one LLM provider must be configured to use the Assistant.",
-                        )
-                        .color(Color::Muted),
-                    ),
-            )
-            .child(
-                v_flex()
-                    .p(DynamicSpacing::Base16.rems(cx))
-                    .mt_1()
-                    .gap_6()
-                    .flex_1()
-                    .children(provider_views),
-            )
-            .into_any();
-
-        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
-        // because we couldn't the element to take up the size of the parent.
-        canvas(
-            move |bounds, cx| {
-                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
-                element
-            },
-            |_, mut element, cx| {
-                element.paint(cx);
-            },
-        )
-        .flex_1()
-        .w_full()
-    }
-}
-
-pub enum ConfigurationViewEvent {
-    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
-}
-
-impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
-
-impl FocusableView for ConfigurationView {
-    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Item for ConfigurationView {
-    type Event = ConfigurationViewEvent;
-
-    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
-        Some("Configuration".into())
-    }
-}