Allow attaching text threads as context (#29947)

Release Notes:

- N/A

---------

Co-authored-by: Michael Sloan <mgsloan@gmail.com>
This commit is contained in:
Max Brunsfeld 2025-05-05 13:59:21 -07:00 committed by GitHub
parent 7f868a2eff
commit dd79c29af9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 784 additions and 245 deletions

View file

@ -21,6 +21,7 @@ client.workspace = true
collections.workspace = true
component.workspace = true
db.workspace = true
futures.workspace = true
gpui.workspace = true
languages.workspace = true
log.workspace = true
@ -30,6 +31,7 @@ prompt_store.workspace = true
serde.workspace = true
ui.workspace = true
ui_input.workspace = true
util.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
assistant_tool.workspace = true

View file

@ -8,7 +8,7 @@ mod preview_support;
use std::iter::Iterator;
use std::sync::Arc;
use agent::{ActiveThread, ThreadStore};
use agent::{ActiveThread, TextThreadStore, ThreadStore};
use client::UserStore;
use component::{ComponentId, ComponentMetadata, components};
use gpui::{
@ -21,11 +21,13 @@ 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 preview_support::active_thread::{
load_preview_text_thread_store, load_preview_thread_store, static_active_thread,
};
use project::Project;
use ui::{Divider, HighlightedLabel, ListItem, ListSubHeader, prelude::*};
use ui_input::SingleLineInput;
use util::ResultExt as _;
use workspace::{AppState, ItemId, SerializableItem, delete_unloaded_items};
use workspace::{Item, Workspace, WorkspaceId, item::ItemEvent};
@ -120,6 +122,7 @@ struct ComponentPreview {
// preview support
thread_store: Option<Entity<ThreadStore>>,
text_thread_store: Option<Entity<TextThreadStore>>,
active_thread: Option<Entity<ActiveThread>>,
}
@ -137,23 +140,29 @@ impl ComponentPreview {
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;
cx.spawn_in(window, async move |entity, cx| {
let thread_store_future =
load_preview_thread_store(workspace_clone.clone(), project_clone.clone(), cx);
let text_thread_store_future =
load_preview_text_thread_store(workspace_clone.clone(), project_clone.clone(), cx);
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 (thread_store_result, text_thread_store_result) =
futures::join!(thread_store_future, text_thread_store_future);
if let (Some(thread_store), Some(text_thread_store)) = (
thread_store_result.log_err(),
text_thread_store_result.log_err(),
) {
entity
.update_in(cx, |this, window, cx| {
this.thread_store = Some(thread_store.clone());
this.text_thread_store = Some(text_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);
@ -195,6 +204,7 @@ impl ComponentPreview {
filter_editor,
filter_text: String::new(),
thread_store: None,
text_thread_store: None,
active_thread: None,
};
@ -220,12 +230,17 @@ impl ComponentPreview {
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() {
if let Some((thread_store, text_thread_store)) = self
.thread_store
.clone()
.zip(self.text_thread_store.clone())
{
let active_thread = static_active_thread(
weak_handle,
project,
language_registry,
thread_store,
text_thread_store,
window,
cx,
);
@ -625,15 +640,11 @@ impl ComponentPreview {
// 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(active_thread) = self.active_thread.clone() {
if let Some(element) = agent::get_agent_preview(
&component.id(),
self.workspace.clone(),
active_thread,
thread_store,
window,
cx,
) {
@ -688,7 +699,6 @@ impl ComponentPreview {
.child(ComponentPreviewPage::new(
component.clone(),
self.workspace.clone(),
self.thread_store.as_ref().map(|ts| ts.downgrade()),
self.active_thread.clone(),
))
.into_any_element()
@ -1037,7 +1047,6 @@ pub struct ComponentPreviewPage {
// languages: Arc<LanguageRegistry>,
component: ComponentMetadata,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
active_thread: Option<Entity<ActiveThread>>,
}
@ -1045,7 +1054,6 @@ impl ComponentPreviewPage {
pub fn new(
component: ComponentMetadata,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<ThreadStore>>,
active_thread: Option<Entity<ActiveThread>>,
// languages: Arc<LanguageRegistry>
) -> Self {
@ -1053,7 +1061,6 @@ impl ComponentPreviewPage {
// languages,
component,
workspace,
thread_store,
active_thread,
}
}
@ -1086,14 +1093,11 @@ 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())
{
let maybe_agent_preview = if let Some(active_thread) = self.active_thread.as_ref() {
agent::get_agent_preview(
&self.component.id(),
self.workspace.clone(),
active_thread.clone(),
thread_store.clone(),
window,
cx,
)

View file

@ -2,31 +2,47 @@ use languages::LanguageRegistry;
use project::Project;
use std::sync::Arc;
use agent::{ActiveThread, ContextStore, MessageSegment, ThreadStore};
use agent::{ActiveThread, ContextStore, MessageSegment, TextThreadStore, ThreadStore};
use anyhow::{Result, anyhow};
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(
pub 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
})
) -> Task<Result<Entity<ThreadStore>>> {
workspace
.update(cx, |_, cx| {
ThreadStore::load(
project.clone(),
cx.new(|_| ToolWorkingSet::default()),
None,
Arc::new(PromptBuilder::new(None).unwrap()),
cx,
)
})
.unwrap_or(Task::ready(Err(anyhow!("workspace dropped"))))
}
pub fn load_preview_text_thread_store(
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
cx: &mut AsyncApp,
) -> Task<Result<Entity<TextThreadStore>>> {
workspace
.update(cx, |_, cx| {
TextThreadStore::new(
project.clone(),
Arc::new(PromptBuilder::new(None).unwrap()),
Default::default(),
cx,
)
})
.unwrap_or(Task::ready(Err(anyhow!("workspace dropped"))))
}
pub fn static_active_thread(
@ -34,6 +50,7 @@ pub fn static_active_thread(
project: Entity<Project>,
language_registry: Arc<LanguageRegistry>,
thread_store: Entity<ThreadStore>,
text_thread_store: Entity<TextThreadStore>,
window: &mut Window,
cx: &mut App,
) -> Entity<ActiveThread> {
@ -59,6 +76,7 @@ pub fn static_active_thread(
ActiveThread::new(
thread,
thread_store,
text_thread_store,
context_store,
language_registry,
workspace.clone(),