From 672a1dd5537dda4114a0814dd361b9bc0ce1778d Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 1 May 2025 23:03:06 -0400 Subject: [PATCH] Add Agent Preview trait (#29760) Like the title says Release Notes: - N/A --- Cargo.lock | 3 + crates/agent/src/assistant.rs | 2 + crates/agent/src/message_editor.rs | 57 ++++- crates/agent/src/ui.rs | 3 + crates/agent/src/ui/agent_preview.rs | 99 +++++++++ crates/agent/src/ui/upsell.rs | 163 ++++++++++++++ crates/agent/src/ui/usage_banner.rs | 4 + crates/component_preview/Cargo.toml | 11 +- .../src/component_preview.rs | 203 ++++++++++++++++-- .../component_preview/src/preview_support.rs | 1 + .../src/preview_support/active_thread.rs | 69 ++++++ 11 files changed, 595 insertions(+), 20 deletions(-) create mode 100644 crates/agent/src/ui/agent_preview.rs create mode 100644 crates/agent/src/ui/upsell.rs create mode 100644 crates/component_preview/src/preview_support.rs create mode 100644 crates/component_preview/src/preview_support/active_thread.rs diff --git a/Cargo.lock b/Cargo.lock index 6fed6289e3..c499cd2f2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3239,7 +3239,9 @@ dependencies = [ name = "component_preview" version = "0.1.0" dependencies = [ + "agent", "anyhow", + "assistant_tool", "client", "collections", "component", @@ -3249,6 +3251,7 @@ dependencies = [ "log", "notifications", "project", + "prompt_store", "serde", "ui", "ui_input", diff --git a/crates/agent/src/assistant.rs b/crates/agent/src/assistant.rs index 3fe77a27da..aa919cc561 100644 --- a/crates/agent/src/assistant.rs +++ b/crates/agent/src/assistant.rs @@ -46,6 +46,8 @@ pub use crate::inline_assistant::InlineAssistant; pub use crate::thread::{Message, MessageSegment, Thread, ThreadEvent}; pub use crate::thread_store::ThreadStore; pub use agent_diff::{AgentDiff, AgentDiffToolbar}; +pub use context_store::ContextStore; +pub use ui::{all_agent_previews, get_agent_preview}; actions!( agent, diff --git a/crates/agent/src/message_editor.rs b/crates/agent/src/message_editor.rs index 5fd464d05e..3cffb4b71e 100644 --- a/crates/agent/src/message_editor.rs +++ b/crates/agent/src/message_editor.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use crate::assistant_model_selector::{AssistantModelSelector, ModelType}; use crate::context::{ContextLoadResult, load_context}; use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip}; -use crate::ui::AnimatedLabel; +use crate::ui::{AgentPreview, AnimatedLabel}; use buffer_diff::BufferDiff; use collections::HashSet; use editor::actions::{MoveUp, Paste}; @@ -42,10 +42,11 @@ use crate::profile_selector::ProfileSelector; use crate::thread::{Thread, TokenUsageRatio}; use crate::thread_store::ThreadStore; use crate::{ - AgentDiff, Chat, ExpandMessageEditor, NewThread, OpenAgentDiff, RemoveAllContext, - ToggleContextPicker, ToggleProfileSelector, + ActiveThread, AgentDiff, Chat, ExpandMessageEditor, NewThread, OpenAgentDiff, RemoveAllContext, + ToggleContextPicker, ToggleProfileSelector, register_agent_preview, }; +#[derive(RegisterComponent)] pub struct MessageEditor { thread: Entity, incompatible_tools_state: Entity, @@ -1202,3 +1203,53 @@ impl Render for MessageEditor { }) } } + +impl Component for MessageEditor { + fn scope() -> ComponentScope { + ComponentScope::Agent + } +} + +impl AgentPreview for MessageEditor { + fn create_preview( + workspace: WeakEntity, + active_thread: Entity, + thread_store: WeakEntity, + window: &mut Window, + cx: &mut App, + ) -> Option { + if let Some(workspace_entity) = workspace.upgrade() { + let fs = workspace_entity.read(cx).app_state().fs.clone(); + let weak_project = workspace_entity.read(cx).project().clone().downgrade(); + let context_store = cx.new(|_cx| ContextStore::new(weak_project, None)); + let thread = active_thread.read(cx).thread().clone(); + + let example_message_editor = cx.new(|cx| { + MessageEditor::new( + fs, + workspace, + context_store, + None, + thread_store, + thread, + window, + cx, + ) + }); + + Some( + v_flex() + .gap_4() + .children(vec![single_example( + "Default", + example_message_editor.clone().into_any_element(), + )]) + .into_any_element(), + ) + } else { + None + } + } +} + +register_agent_preview!(MessageEditor); diff --git a/crates/agent/src/ui.rs b/crates/agent/src/ui.rs index d0ef9deafe..4e9465b811 100644 --- a/crates/agent/src/ui.rs +++ b/crates/agent/src/ui.rs @@ -1,9 +1,12 @@ mod agent_notification; +pub mod agent_preview; mod animated_label; mod context_pill; +mod upsell; mod usage_banner; pub use agent_notification::*; +pub use agent_preview::*; pub use animated_label::*; pub use context_pill::*; pub use usage_banner::*; diff --git a/crates/agent/src/ui/agent_preview.rs b/crates/agent/src/ui/agent_preview.rs new file mode 100644 index 0000000000..c6b7010db4 --- /dev/null +++ b/crates/agent/src/ui/agent_preview.rs @@ -0,0 +1,99 @@ +use collections::HashMap; +use component::ComponentId; +use gpui::{App, Entity, WeakEntity}; +use linkme::distributed_slice; +use std::sync::OnceLock; +use ui::{AnyElement, Component, Window}; +use workspace::Workspace; + +use crate::{ActiveThread, ThreadStore}; + +/// Function type for creating agent component previews +pub type PreviewFn = fn( + WeakEntity, + Entity, + WeakEntity, + &mut Window, + &mut App, +) -> Option; + +/// Distributed slice for preview registration functions +#[distributed_slice] +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()) + } + + /// Static method to create a preview for this component type + fn create_preview( + workspace: WeakEntity, + active_thread: Entity, + thread_store: WeakEntity, + window: &mut Window, + cx: &mut App, + ) -> Option + where + Self: Sized; +} + +/// Register an agent preview for the given component type +#[macro_export] +macro_rules! register_agent_preview { + ($type:ty) => { + #[linkme::distributed_slice($crate::ui::agent_preview::__ALL_AGENT_PREVIEWS)] + static __REGISTER_AGENT_PREVIEW: fn() -> ( + component::ComponentId, + $crate::ui::agent_preview::PreviewFn, + ) = || { + ( + <$type as $crate::ui::agent_preview::AgentPreview>::id(), + <$type as $crate::ui::agent_preview::AgentPreview>::create_preview, + ) + }; + }; +} + +/// Lazy initialized registry of preview functions +static AGENT_PREVIEW_REGISTRY: OnceLock> = OnceLock::new(); + +/// Initialize the agent preview registry if needed +fn get_or_init_registry() -> &'static HashMap { + AGENT_PREVIEW_REGISTRY.get_or_init(|| { + let mut map = HashMap::default(); + for register_fn in __ALL_AGENT_PREVIEWS.iter() { + let (id, preview_fn) = register_fn(); + map.insert(id, preview_fn); + } + map + }) +} + +/// Get a specific agent preview by component ID. +pub fn get_agent_preview( + id: &ComponentId, + workspace: WeakEntity, + active_thread: Entity, + thread_store: WeakEntity, + window: &mut Window, + cx: &mut App, +) -> Option { + let registry = get_or_init_registry(); + registry + .get(id) + .and_then(|preview_fn| preview_fn(workspace, active_thread, thread_store, window, cx)) +} + +/// Get all registered agent previews. +pub fn all_agent_previews() -> Vec { + let registry = get_or_init_registry(); + registry.keys().cloned().collect() +} diff --git a/crates/agent/src/ui/upsell.rs b/crates/agent/src/ui/upsell.rs new file mode 100644 index 0000000000..d66d8c5ebb --- /dev/null +++ b/crates/agent/src/ui/upsell.rs @@ -0,0 +1,163 @@ +use component::{Component, ComponentScope, single_example}; +use gpui::{ + AnyElement, App, ClickEvent, IntoElement, ParentElement, RenderOnce, SharedString, Styled, + Window, +}; +use theme::ActiveTheme; +use ui::{ + Button, ButtonCommon, ButtonStyle, Checkbox, Clickable, Color, Label, LabelCommon, + RegisterComponent, ToggleState, h_flex, v_flex, +}; + +/// A component that displays an upsell message with a call-to-action button +/// +/// # Example +/// ``` +/// let upsell = Upsell::new( +/// "Upgrade to Zed Pro", +/// "Get unlimited access to AI features and more", +/// "Upgrade Now", +/// Box::new(|_, _window, cx| { +/// cx.open_url("https://zed.dev/pricing"); +/// }), +/// Box::new(|_, _window, cx| { +/// // Handle dismiss +/// }), +/// Box::new(|checked, window, cx| { +/// // Handle don't show again +/// }), +/// ); +/// ``` +#[derive(IntoElement, RegisterComponent)] +pub struct Upsell { + title: SharedString, + message: SharedString, + cta_text: SharedString, + on_click: Box, + on_dismiss: Box, + on_dont_show_again: Box, +} + +impl Upsell { + /// Create a new upsell component + pub fn new( + title: impl Into, + message: impl Into, + cta_text: impl Into, + on_click: Box, + on_dismiss: Box, + on_dont_show_again: Box, + ) -> Self { + Self { + title: title.into(), + message: message.into(), + cta_text: cta_text.into(), + on_click, + on_dismiss, + on_dont_show_again, + } + } +} + +impl RenderOnce for Upsell { + fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { + v_flex() + .w_full() + .p_4() + .gap_3() + .bg(cx.theme().colors().surface_background) + .rounded_md() + .border_1() + .border_color(cx.theme().colors().border) + .child( + v_flex() + .gap_1() + .child( + Label::new(self.title) + .size(ui::LabelSize::Large) + .weight(gpui::FontWeight::BOLD), + ) + .child(Label::new(self.message).color(Color::Muted)), + ) + .child( + h_flex() + .w_full() + .justify_between() + .items_center() + .child( + h_flex() + .items_center() + .gap_1() + .child( + Checkbox::new("dont-show-again", ToggleState::Unselected).on_click( + move |_, window, cx| { + (self.on_dont_show_again)(true, window, cx); + }, + ), + ) + .child( + Label::new("Don't show again") + .color(Color::Muted) + .size(ui::LabelSize::Small), + ), + ) + .child( + h_flex() + .gap_2() + .child( + Button::new("dismiss-button", "Dismiss") + .style(ButtonStyle::Subtle) + .on_click(self.on_dismiss), + ) + .child( + Button::new("cta-button", self.cta_text) + .style(ButtonStyle::Filled) + .on_click(self.on_click), + ), + ), + ) + } +} + +impl Component for Upsell { + fn scope() -> ComponentScope { + ComponentScope::Agent + } + + fn name() -> &'static str { + "Upsell" + } + + fn description() -> Option<&'static str> { + Some("A promotional component that displays a message with a call-to-action.") + } + + fn preview(window: &mut Window, cx: &mut App) -> Option { + let examples = vec![ + single_example( + "Default", + Upsell::new( + "Upgrade to Zed Pro", + "Get unlimited access to AI features and more with Zed Pro. Unlock advanced AI capabilities and other premium features.", + "Upgrade Now", + Box::new(|_, _, _| {}), + Box::new(|_, _, _| {}), + Box::new(|_, _, _| {}), + ).render(window, cx).into_any_element(), + ), + single_example( + "Short Message", + Upsell::new( + "Try Zed Pro for free", + "Start your 7-day trial today.", + "Start Trial", + Box::new(|_, _, _| {}), + Box::new(|_, _, _| {}), + Box::new(|_, _, _| {}), + ).render(window, cx).into_any_element(), + ), + ]; + + Some(v_flex().gap_4().children(examples).into_any_element()) + } +} diff --git a/crates/agent/src/ui/usage_banner.rs b/crates/agent/src/ui/usage_banner.rs index 85b859de10..73bc270185 100644 --- a/crates/agent/src/ui/usage_banner.rs +++ b/crates/agent/src/ui/usage_banner.rs @@ -98,6 +98,10 @@ impl RenderOnce for UsageBanner { } impl Component for UsageBanner { + fn scope() -> ComponentScope { + ComponentScope::Agent + } + fn sort_name() -> &'static str { "AgentUsageBanner" } diff --git a/crates/component_preview/Cargo.toml b/crates/component_preview/Cargo.toml index bfa19fa60d..61d207d0dc 100644 --- a/crates/component_preview/Cargo.toml +++ b/crates/component_preview/Cargo.toml @@ -15,18 +15,21 @@ path = "src/component_preview.rs" default = [] [dependencies] +agent.workspace = true +anyhow.workspace = true client.workspace = true collections.workspace = true component.workspace = true +db.workspace = true gpui.workspace = true languages.workspace = true -notifications.workspace = true log.workspace = true +notifications.workspace = true project.workspace = true +prompt_store.workspace = true +serde.workspace = true ui.workspace = true ui_input.workspace = true workspace-hack.workspace = true workspace.workspace = true -db.workspace = true -anyhow.workspace = true -serde.workspace = true +assistant_tool.workspace = true diff --git a/crates/component_preview/src/component_preview.rs b/crates/component_preview/src/component_preview.rs index f026854324..1072e52bad 100644 --- a/crates/component_preview/src/component_preview.rs +++ b/crates/component_preview/src/component_preview.rs @@ -3,10 +3,12 @@ //! A view for exploring Zed components. mod persistence; +mod preview_support; use std::iter::Iterator; use std::sync::Arc; +use agent::{ActiveThread, ThreadStore}; use client::UserStore; use component::{ComponentId, ComponentMetadata, components}; use gpui::{ @@ -19,6 +21,7 @@ use gpui::{ListState, ScrollHandle, ScrollStrategy, UniformListScrollHandle}; use languages::LanguageRegistry; use notifications::status_toast::{StatusToast, ToastIcon}; use persistence::COMPONENT_PREVIEW_DB; +use preview_support::active_thread::{load_preview_thread_store, static_active_thread}; use project::Project; use ui::{Divider, HighlightedLabel, ListItem, ListSubHeader, prelude::*}; @@ -33,6 +36,7 @@ pub fn init(app_state: Arc, cx: &mut App) { cx.observe_new(move |workspace: &mut Workspace, _window, cx| { let app_state = app_state.clone(); + let project = workspace.project().clone(); let weak_workspace = cx.entity().downgrade(); workspace.register_action( @@ -45,6 +49,7 @@ pub fn init(app_state: Arc, cx: &mut App) { let component_preview = cx.new(|cx| { ComponentPreview::new( weak_workspace.clone(), + project.clone(), language_registry, user_store, None, @@ -52,6 +57,7 @@ pub fn init(app_state: Arc, cx: &mut App) { window, cx, ) + .expect("Failed to create component preview") }); workspace.add_item_to_active_pane( @@ -69,6 +75,7 @@ pub fn init(app_state: Arc, cx: &mut App) { enum PreviewEntry { AllComponents, + ActiveThread, Separator, Component(ComponentMetadata, Option>), SectionHeader(SharedString), @@ -91,6 +98,7 @@ enum PreviewPage { #[default] AllComponents, Component(ComponentId), + ActiveThread, } struct ComponentPreview { @@ -102,24 +110,63 @@ 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, + >, + >, cursor_index: usize, language_registry: Arc, workspace: WeakEntity, + project: Entity, user_store: Entity, filter_editor: Entity, filter_text: String, + + // preview support + thread_store: Option>, + active_thread: Option>, } impl ComponentPreview { pub fn new( workspace: WeakEntity, + project: Entity, language_registry: Arc, user_store: Entity, selected_index: impl Into>, active_page: Option, window: &mut Window, cx: &mut Context, - ) -> Self { + ) -> anyhow::Result { + let workspace_clone = workspace.clone(); + let project_clone = project.clone(); + + let entity = cx.weak_entity(); + window + .spawn(cx, async move |cx| { + let thread_store_task = + load_preview_thread_store(workspace_clone.clone(), project_clone.clone(), cx) + .await; + + if let Ok(thread_store) = thread_store_task.await { + entity + .update_in(cx, |this, window, cx| { + this.thread_store = Some(thread_store.clone()); + this.create_active_thread(window, cx); + }) + .ok(); + } + }) + .detach(); + let sorted_components = components().all_sorted(); let selected_index = selected_index.into().unwrap_or(0); let active_page = active_page.unwrap_or(PreviewPage::AllComponents); @@ -143,6 +190,40 @@ 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 mut component_preview = Self { workspace_id: None, focus_handle: cx.focus_handle(), @@ -151,13 +232,17 @@ impl ComponentPreview { language_registry, user_store, workspace, + project, active_page, component_map: components().0, components: sorted_components, component_list, + agent_previews, cursor_index: selected_index, filter_editor, filter_text: String::new(), + thread_store: None, + active_thread: None, }; if component_preview.cursor_index > 0 { @@ -169,13 +254,41 @@ impl ComponentPreview { let focus_handle = component_preview.filter_editor.read(cx).focus_handle(cx); window.focus(&focus_handle); - component_preview + Ok(component_preview) + } + + pub fn create_active_thread( + &mut self, + window: &mut Window, + cx: &mut Context, + ) -> &mut Self { + let workspace = self.workspace.clone(); + let language_registry = self.language_registry.clone(); + let weak_handle = self.workspace.clone(); + if let Some(workspace) = workspace.upgrade() { + let project = workspace.read(cx).project().clone(); + if let Some(thread_store) = self.thread_store.clone() { + let active_thread = static_active_thread( + weak_handle, + project, + language_registry, + thread_store, + window, + cx, + ); + self.active_thread = Some(active_thread); + cx.notify(); + } + } + + self } pub fn active_page_id(&self, _cx: &App) -> ActivePageId { match &self.active_page { PreviewPage::AllComponents => ActivePageId::default(), PreviewPage::Component(component_id) => ActivePageId(component_id.0.to_string()), + PreviewPage::ActiveThread => ActivePageId("active_thread".to_string()), } } @@ -289,6 +402,7 @@ impl ComponentPreview { // Always show all components first entries.push(PreviewEntry::AllComponents); + entries.push(PreviewEntry::ActiveThread); entries.push(PreviewEntry::Separator); let mut scopes: Vec<_> = scope_groups @@ -389,6 +503,19 @@ impl ComponentPreview { })) .into_any_element() } + PreviewEntry::ActiveThread => { + let selected = self.active_page == PreviewPage::ActiveThread; + + ListItem::new(ix) + .child(Label::new("Active Thread").color(Color::Default)) + .selectable(true) + .toggle_state(selected) + .inset(true) + .on_click(cx.listener(move |this, _, _, cx| { + this.set_active_page(PreviewPage::ActiveThread, cx); + })) + .into_any_element() + } PreviewEntry::Separator => ListItem::new(ix) .child( h_flex() @@ -471,6 +598,7 @@ impl ComponentPreview { .render_scope_header(ix, shared_string.clone(), window, cx) .into_any_element(), PreviewEntry::AllComponents => div().w_full().h_0().into_any_element(), + PreviewEntry::ActiveThread => div().w_full().h_0().into_any_element(), PreviewEntry::Separator => div().w_full().h_0().into_any_element(), }) .unwrap() @@ -595,6 +723,41 @@ impl ComponentPreview { } } + fn render_active_thread( + &self, + window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + v_flex() + .id("render-active-thread") + .size_full() + .child( + v_flex().children(self.agent_previews.iter().filter_map(|preview_fn| { + if let (Some(thread_store), Some(active_thread)) = ( + self.thread_store.as_ref().map(|ts| ts.downgrade()), + self.active_thread.clone(), + ) { + preview_fn( + self, + self.workspace.clone(), + active_thread, + thread_store, + window, + cx, + ) + .map(|element| div().child(element)) + } else { + None + } + })), + ) + .children(self.active_thread.clone().map(|thread| thread.clone())) + .when_none(&self.active_thread.clone(), |this| { + this.child("No active thread") + }) + .into_any_element() + } + fn test_status_toast(&self, cx: &mut Context) { if let Some(workspace) = self.workspace.upgrade() { workspace.update(cx, |workspace, cx| { @@ -704,6 +867,9 @@ impl Render for ComponentPreview { PreviewPage::Component(id) => self .render_component_page(&id, window, cx) .into_any_element(), + PreviewPage::ActiveThread => { + self.render_active_thread(window, cx).into_any_element() + } }), ) } @@ -759,20 +925,28 @@ impl Item for ComponentPreview { let language_registry = self.language_registry.clone(); let user_store = self.user_store.clone(); let weak_workspace = self.workspace.clone(); + let project = self.project.clone(); let selected_index = self.cursor_index; let active_page = self.active_page.clone(); - Some(cx.new(|cx| { - Self::new( - weak_workspace, - language_registry, - user_store, - selected_index, - Some(active_page), - window, - cx, - ) - })) + let self_result = Self::new( + weak_workspace, + project, + language_registry, + user_store, + selected_index, + Some(active_page), + window, + cx, + ); + + match self_result { + Ok(preview) => Some(cx.new(|_cx| preview)), + Err(e) => { + log::error!("Failed to clone component preview: {}", e); + None + } + } } fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) { @@ -838,10 +1012,12 @@ impl SerializableItem for ComponentPreview { let user_store = user_store.clone(); let language_registry = language_registry.clone(); let weak_workspace = workspace.clone(); + let project = project.clone(); cx.update(move |window, cx| { Ok(cx.new(|cx| { ComponentPreview::new( weak_workspace, + project, language_registry, user_store, None, @@ -849,6 +1025,7 @@ impl SerializableItem for ComponentPreview { window, cx, ) + .expect("Failed to create component preview") })) })? }) diff --git a/crates/component_preview/src/preview_support.rs b/crates/component_preview/src/preview_support.rs new file mode 100644 index 0000000000..3b5d6a6df6 --- /dev/null +++ b/crates/component_preview/src/preview_support.rs @@ -0,0 +1 @@ +pub mod active_thread; diff --git a/crates/component_preview/src/preview_support/active_thread.rs b/crates/component_preview/src/preview_support/active_thread.rs new file mode 100644 index 0000000000..725b9acdae --- /dev/null +++ b/crates/component_preview/src/preview_support/active_thread.rs @@ -0,0 +1,69 @@ +use languages::LanguageRegistry; +use project::Project; +use std::sync::Arc; + +use agent::{ActiveThread, ContextStore, MessageSegment, ThreadStore}; +use assistant_tool::ToolWorkingSet; +use gpui::{AppContext, AsyncApp, Entity, Task, WeakEntity}; +use prompt_store::PromptBuilder; +use ui::{App, Window}; +use workspace::Workspace; + +pub async fn load_preview_thread_store( + workspace: WeakEntity, + project: Entity, + cx: &mut AsyncApp, +) -> Task>> { + cx.spawn(async move |cx| { + workspace + .update(cx, |_, cx| { + ThreadStore::load( + project.clone(), + cx.new(|_| ToolWorkingSet::default()), + None, + Arc::new(PromptBuilder::new(None).unwrap()), + cx, + ) + })? + .await + }) +} + +pub fn static_active_thread( + workspace: WeakEntity, + project: Entity, + language_registry: Arc, + thread_store: Entity, + window: &mut Window, + cx: &mut App, +) -> Entity { + let context_store = + cx.new(|_| ContextStore::new(project.downgrade(), Some(thread_store.downgrade()))); + + let thread = thread_store.update(cx, |thread_store, cx| thread_store.create_thread(cx)); + thread.update(cx, |thread, cx| { + thread.insert_assistant_message(vec![ + MessageSegment::Text("I'll help you fix the lifetime error in your `cx.spawn` call. When working with async operations in GPUI, there are specific patterns to follow for proper lifetime management.".to_string()), + MessageSegment::Text("\n\nLet's look at what's happening in your code:".to_string()), + MessageSegment::Text("\n\n---\n\nLet's check the current state of the active_thread.rs file to understand what might have changed:".to_string()), + MessageSegment::Text("\n\n---\n\nLooking at the implementation of `load_preview_thread_store` and understanding GPUI's async patterns, here's the issue:".to_string()), + MessageSegment::Text("\n\n1. `load_preview_thread_store` returns a `Task>>`, which means it's already a task".to_string()), + MessageSegment::Text("\n2. When you call this function inside another `spawn` call, you're nesting tasks incorrectly".to_string()), + MessageSegment::Text("\n3. The `this` parameter you're trying to use in your closure has the wrong context".to_string()), + MessageSegment::Text("\n\nHere's the correct way to implement this:".to_string()), + MessageSegment::Text("\n\n---\n\nThe problem is in how you're setting up the async closure and trying to reference variables like `window` and `language_registry` that aren't accessible in that scope.".to_string()), + MessageSegment::Text("\n\nHere's how to fix it:".to_string()), + ], cx); + }); + cx.new(|cx| { + ActiveThread::new( + thread, + thread_store, + context_store, + language_registry, + workspace.clone(), + window, + cx, + ) + }) +}