Detailed changes
@@ -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",
@@ -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,
@@ -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<Thread>,
incompatible_tools_state: Entity<IncompatibleToolsState>,
@@ -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<Workspace>,
+ active_thread: Entity<ActiveThread>,
+ thread_store: WeakEntity<ThreadStore>,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> Option<AnyElement> {
+ 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);
@@ -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::*;
@@ -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<Workspace>,
+ Entity<ActiveThread>,
+ WeakEntity<ThreadStore>,
+ &mut Window,
+ &mut App,
+) -> Option<AnyElement>;
+
+/// 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<Workspace>,
+ active_thread: Entity<ActiveThread>,
+ thread_store: WeakEntity<ThreadStore>,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> Option<AnyElement>
+ 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<HashMap<ComponentId, PreviewFn>> = OnceLock::new();
+
+/// Initialize the agent preview registry if needed
+fn get_or_init_registry() -> &'static HashMap<ComponentId, PreviewFn> {
+ 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<Workspace>,
+ active_thread: Entity<ActiveThread>,
+ thread_store: WeakEntity<ThreadStore>,
+ window: &mut Window,
+ cx: &mut App,
+) -> Option<AnyElement> {
+ 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<ComponentId> {
+ let registry = get_or_init_registry();
+ registry.keys().cloned().collect()
+}
@@ -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<dyn Fn(&ClickEvent, &mut Window, &mut App)>,
+ on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>,
+ on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App)>,
+}
+
+impl Upsell {
+ /// Create a new upsell component
+ pub fn new(
+ title: impl Into<SharedString>,
+ message: impl Into<SharedString>,
+ cta_text: impl Into<SharedString>,
+ on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>,
+ on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>,
+ on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App)>,
+ ) -> 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<AnyElement> {
+ 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())
+ }
+}
@@ -98,6 +98,10 @@ impl RenderOnce for UsageBanner {
}
impl Component for UsageBanner {
+ fn scope() -> ComponentScope {
+ ComponentScope::Agent
+ }
+
fn sort_name() -> &'static str {
"AgentUsageBanner"
}
@@ -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
@@ -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<AppState>, 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<AppState>, 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<AppState>, 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<AppState>, cx: &mut App) {
enum PreviewEntry {
AllComponents,
+ ActiveThread,
Separator,
Component(ComponentMetadata, Option<Vec<usize>>),
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<ComponentMetadata>,
component_list: ListState,
+ agent_previews: Vec<
+ Box<
+ dyn Fn(
+ &Self,
+ WeakEntity<Workspace>,
+ Entity<ActiveThread>,
+ WeakEntity<ThreadStore>,
+ &mut Window,
+ &mut App,
+ ) -> Option<AnyElement>,
+ >,
+ >,
cursor_index: usize,
language_registry: Arc<LanguageRegistry>,
workspace: WeakEntity<Workspace>,
+ project: Entity<Project>,
user_store: Entity<UserStore>,
filter_editor: Entity<SingleLineInput>,
filter_text: String,
+
+ // preview support
+ thread_store: Option<Entity<ThreadStore>>,
+ active_thread: Option<Entity<ActiveThread>>,
}
impl ComponentPreview {
pub fn new(
workspace: WeakEntity<Workspace>,
+ project: Entity<Project>,
language_registry: Arc<LanguageRegistry>,
user_store: Entity<UserStore>,
selected_index: impl Into<Option<usize>>,
active_page: Option<PreviewPage>,
window: &mut Window,
cx: &mut Context<Self>,
- ) -> Self {
+ ) -> anyhow::Result<Self> {
+ 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<Workspace>,
+ active_thread: Entity<ActiveThread>,
+ thread_store: WeakEntity<ThreadStore>,
+ window: &mut Window,
+ cx: &mut App| {
+ agent::get_agent_preview(
+ &id,
+ workspace,
+ active_thread,
+ thread_store,
+ window,
+ cx,
+ )
+ },
+ )
+ as Box<
+ dyn Fn(
+ &ComponentPreview,
+ WeakEntity<Workspace>,
+ Entity<ActiveThread>,
+ WeakEntity<ThreadStore>,
+ &mut Window,
+ &mut App,
+ ) -> Option<AnyElement>,
+ >
+ })
+ .collect::<Vec<_>>();
+
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<Self>,
+ ) -> &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<Self>,
+ ) -> 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<Self>) {
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")
}))
})?
})
@@ -0,0 +1 @@
+pub mod active_thread;
@@ -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<Workspace>,
+ project: Entity<Project>,
+ cx: &mut AsyncApp,
+) -> Task<anyhow::Result<Entity<ThreadStore>>> {
+ 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<Workspace>,
+ project: Entity<Project>,
+ language_registry: Arc<LanguageRegistry>,
+ thread_store: Entity<ThreadStore>,
+ window: &mut Window,
+ cx: &mut App,
+) -> Entity<ActiveThread> {
+ 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<anyhow::Result<Entity<ThreadStore>>>`, 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,
+ )
+ })
+}