From 3bd7ae6e5ba65f10d32b6439ca9f7dce4f2a4b0b Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 2 May 2025 09:32:59 -0400 Subject: [PATCH] Standardize agent previews (#29790) This PR makes agent previews render like any other preview in the component preview list & pages. Page: ![CleanShot 2025-05-02 at 09 17 12@2x](https://github.com/user-attachments/assets/8b611380-b686-4fd6-9c76-de27e35b0b38) List: ![CleanShot 2025-05-02 at 09 17 33@2x](https://github.com/user-attachments/assets/ab063649-dc3c-4c95-969b-c3795b2197f2) Release Notes: - N/A --- crates/agent/src/message_editor.rs | 2 +- crates/agent/src/ui/agent_preview.rs | 25 +-- crates/component/src/component.rs | 5 +- .../src/component_preview.rs | 187 ++++++++++-------- 4 files changed, 114 insertions(+), 105 deletions(-) diff --git a/crates/agent/src/message_editor.rs b/crates/agent/src/message_editor.rs index 3cffb4b71e..edbfac578a 100644 --- a/crates/agent/src/message_editor.rs +++ b/crates/agent/src/message_editor.rs @@ -1211,7 +1211,7 @@ impl Component for MessageEditor { } impl AgentPreview for MessageEditor { - fn create_preview( + fn agent_preview( workspace: WeakEntity, active_thread: Entity, thread_store: WeakEntity, diff --git a/crates/agent/src/ui/agent_preview.rs b/crates/agent/src/ui/agent_preview.rs index c6b7010db4..fafb61114f 100644 --- a/crates/agent/src/ui/agent_preview.rs +++ b/crates/agent/src/ui/agent_preview.rs @@ -3,7 +3,7 @@ use component::ComponentId; use gpui::{App, Entity, WeakEntity}; use linkme::distributed_slice; use std::sync::OnceLock; -use ui::{AnyElement, Component, Window}; +use ui::{AnyElement, Component, ComponentScope, Window}; use workspace::Workspace; use crate::{ActiveThread, ThreadStore}; @@ -22,27 +22,20 @@ pub type PreviewFn = fn( pub static __ALL_AGENT_PREVIEWS: [fn() -> (ComponentId, PreviewFn)] = [..]; /// Trait that must be implemented by components that provide agent previews. -pub trait AgentPreview: Component { - /// Get the ID for this component - /// - /// Eventually this will move to the component trait. - fn id() -> ComponentId - where - Self: Sized, - { - ComponentId(Self::name()) +pub trait AgentPreview: Component + Sized { + #[allow(unused)] // We can't know this is used due to the distributed slice + fn scope(&self) -> ComponentScope { + ComponentScope::Agent } /// Static method to create a preview for this component type - fn create_preview( + fn agent_preview( workspace: WeakEntity, active_thread: Entity, thread_store: WeakEntity, window: &mut Window, cx: &mut App, - ) -> Option - where - Self: Sized; + ) -> Option; } /// Register an agent preview for the given component type @@ -55,8 +48,8 @@ macro_rules! register_agent_preview { $crate::ui::agent_preview::PreviewFn, ) = || { ( - <$type as $crate::ui::agent_preview::AgentPreview>::id(), - <$type as $crate::ui::agent_preview::AgentPreview>::create_preview, + <$type as component::Component>::id(), + <$type as $crate::ui::agent_preview::AgentPreview>::agent_preview, ) }; }; diff --git a/crates/component/src/component.rs b/crates/component/src/component.rs index 8394c91b98..1180b2ff94 100644 --- a/crates/component/src/component.rs +++ b/crates/component/src/component.rs @@ -18,6 +18,9 @@ pub trait Component { fn name() -> &'static str { std::any::type_name::() } + fn id() -> ComponentId { + ComponentId(Self::name()) + } /// Returns a name that the component should be sorted by. /// /// Implement this if the component should be sorted in an alternate order than its name. @@ -81,7 +84,7 @@ pub fn register_component() { let component_data = (T::scope(), T::name(), T::sort_name(), T::description()); let mut data = COMPONENT_DATA.write(); data.components.push(component_data); - data.previews.insert(T::name(), T::preview); + data.previews.insert(T::id().0, T::preview); } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/component_preview/src/component_preview.rs b/crates/component_preview/src/component_preview.rs index 1072e52bad..933053dbf6 100644 --- a/crates/component_preview/src/component_preview.rs +++ b/crates/component_preview/src/component_preview.rs @@ -110,18 +110,7 @@ struct ComponentPreview { active_page: PreviewPage, components: Vec, component_list: ListState, - agent_previews: Vec< - Box< - dyn Fn( - &Self, - WeakEntity, - Entity, - WeakEntity, - &mut Window, - &mut App, - ) -> Option, - >, - >, + agent_previews: Vec, cursor_index: usize, language_registry: Arc, workspace: WeakEntity, @@ -191,38 +180,7 @@ impl ComponentPreview { ); // Initialize agent previews - let agent_previews = agent::all_agent_previews() - .into_iter() - .map(|id| { - Box::new( - move |_self: &ComponentPreview, - workspace: WeakEntity, - active_thread: Entity, - thread_store: WeakEntity, - window: &mut Window, - cx: &mut App| { - agent::get_agent_preview( - &id, - workspace, - active_thread, - thread_store, - window, - cx, - ) - }, - ) - as Box< - dyn Fn( - &ComponentPreview, - WeakEntity, - Entity, - WeakEntity, - &mut Window, - &mut App, - ) -> Option, - > - }) - .collect::>(); + let agent_previews = agent::all_agent_previews(); let mut component_preview = Self { workspace_id: None, @@ -635,44 +593,65 @@ impl ComponentPreview { let description = component.description(); - v_flex() - .py_2() - .child( - v_flex() - .border_1() - .border_color(cx.theme().colors().border) - .rounded_sm() - .w_full() - .gap_4() - .py_4() - .px_6() - .flex_none() - .child( - v_flex() - .gap_1() - .child( - h_flex().gap_1().text_xl().child(div().child(name)).when( - !matches!(scope, ComponentScope::None), - |this| { - this.child(div().opacity(0.5).child(format!("({})", scope))) - }, - ), + // Build the content container + let mut preview_container = v_flex().py_2().child( + v_flex() + .border_1() + .border_color(cx.theme().colors().border) + .rounded_sm() + .w_full() + .gap_4() + .py_4() + .px_6() + .flex_none() + .child( + v_flex() + .gap_1() + .child( + h_flex() + .gap_1() + .text_xl() + .child(div().child(name)) + .when(!matches!(scope, ComponentScope::None), |this| { + this.child(div().opacity(0.5).child(format!("({})", scope))) + }), + ) + .when_some(description, |this, description| { + this.child( + div() + .text_ui_sm(cx) + .text_color(cx.theme().colors().text_muted) + .max_w(px(600.0)) + .child(description), ) - .when_some(description, |this, description| { - this.child( - div() - .text_ui_sm(cx) - .text_color(cx.theme().colors().text_muted) - .max_w(px(600.0)) - .child(description), - ) - }), - ) - .when_some(component.preview(), |this, preview| { - this.children(preview(window, cx)) - }), - ) - .into_any_element() + }), + ), + ); + + // Check if the component's scope is Agent + if scope == ComponentScope::Agent { + if let (Some(thread_store), Some(active_thread)) = ( + self.thread_store.as_ref().map(|ts| ts.downgrade()), + self.active_thread.clone(), + ) { + if let Some(element) = agent::get_agent_preview( + &component.id(), + self.workspace.clone(), + active_thread, + thread_store, + window, + cx, + ) { + preview_container = preview_container.child(element); + } else if let Some(preview) = component.preview() { + preview_container = preview_container.children(preview(window, cx)); + } + } + } else if let Some(preview) = component.preview() { + preview_container = preview_container.children(preview(window, cx)); + } + + preview_container.into_any_element() } fn render_all_components(&self, cx: &Context) -> impl IntoElement { @@ -711,7 +690,12 @@ impl ComponentPreview { v_flex() .id("render-component-page") .size_full() - .child(ComponentPreviewPage::new(component.clone())) + .child(ComponentPreviewPage::new( + component.clone(), + self.workspace.clone(), + self.thread_store.as_ref().map(|ts| ts.downgrade()), + self.active_thread.clone(), + )) .into_any_element() } else { v_flex() @@ -732,13 +716,13 @@ impl ComponentPreview { .id("render-active-thread") .size_full() .child( - v_flex().children(self.agent_previews.iter().filter_map(|preview_fn| { + v_flex().children(self.agent_previews.iter().filter_map(|component_id| { if let (Some(thread_store), Some(active_thread)) = ( self.thread_store.as_ref().map(|ts| ts.downgrade()), self.active_thread.clone(), ) { - preview_fn( - self, + agent::get_agent_preview( + component_id, self.workspace.clone(), active_thread, thread_store, @@ -894,7 +878,7 @@ impl Default for ActivePageId { impl From for ActivePageId { fn from(id: ComponentId) -> Self { - ActivePageId(id.0.to_string()) + Self(id.0.to_string()) } } @@ -1073,16 +1057,25 @@ impl SerializableItem for ComponentPreview { pub struct ComponentPreviewPage { // languages: Arc, component: ComponentMetadata, + workspace: WeakEntity, + thread_store: Option>, + active_thread: Option>, } impl ComponentPreviewPage { pub fn new( component: ComponentMetadata, + workspace: WeakEntity, + thread_store: Option>, + active_thread: Option>, // languages: Arc ) -> Self { Self { // languages, component, + workspace, + thread_store, + active_thread, } } @@ -1113,12 +1106,32 @@ impl ComponentPreviewPage { } fn render_preview(&self, window: &mut Window, cx: &mut App) -> impl IntoElement { + // Try to get agent preview first if we have an active thread + let maybe_agent_preview = if let (Some(thread_store), Some(active_thread)) = + (self.thread_store.as_ref(), self.active_thread.as_ref()) + { + agent::get_agent_preview( + &self.component.id(), + self.workspace.clone(), + active_thread.clone(), + thread_store.clone(), + window, + cx, + ) + } else { + None + }; + v_flex() .flex_1() .px_12() .py_6() .bg(cx.theme().colors().editor_background) - .child(if let Some(preview) = self.component.preview() { + .child(if let Some(element) = maybe_agent_preview { + // Use agent preview if available + element + } else if let Some(preview) = self.component.preview() { + // Fall back to component preview preview(window, cx).unwrap_or_else(|| { div() .child("Failed to load preview. This path should be unreachable")