Allow attaching text threads as context (#29947)
Release Notes: - N/A --------- Co-authored-by: Michael Sloan <mgsloan@gmail.com>
This commit is contained in:
parent
7f868a2eff
commit
dd79c29af9
24 changed files with 784 additions and 245 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -125,6 +125,7 @@ dependencies = [
|
||||||
"time_format",
|
"time_format",
|
||||||
"ui",
|
"ui",
|
||||||
"ui_input",
|
"ui_input",
|
||||||
|
"urlencoding",
|
||||||
"util",
|
"util",
|
||||||
"uuid",
|
"uuid",
|
||||||
"workspace",
|
"workspace",
|
||||||
|
@ -3251,6 +3252,7 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"component",
|
"component",
|
||||||
"db",
|
"db",
|
||||||
|
"futures 0.3.31",
|
||||||
"gpui",
|
"gpui",
|
||||||
"languages",
|
"languages",
|
||||||
"log",
|
"log",
|
||||||
|
@ -3260,6 +3262,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"ui",
|
"ui",
|
||||||
"ui_input",
|
"ui_input",
|
||||||
|
"util",
|
||||||
"workspace",
|
"workspace",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
]
|
]
|
||||||
|
|
|
@ -90,6 +90,7 @@ time.workspace = true
|
||||||
time_format.workspace = true
|
time_format.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
ui_input.workspace = true
|
ui_input.workspace = true
|
||||||
|
urlencoding.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::thread::{
|
||||||
LastRestoreCheckpoint, MessageId, MessageSegment, Thread, ThreadError, ThreadEvent,
|
LastRestoreCheckpoint, MessageId, MessageSegment, Thread, ThreadError, ThreadEvent,
|
||||||
ThreadFeedback,
|
ThreadFeedback,
|
||||||
};
|
};
|
||||||
use crate::thread_store::{RulesLoadingError, ThreadStore};
|
use crate::thread_store::{RulesLoadingError, TextThreadStore, ThreadStore};
|
||||||
use crate::tool_use::{PendingToolUseStatus, ToolUse};
|
use crate::tool_use::{PendingToolUseStatus, ToolUse};
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
|
AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
|
||||||
|
@ -56,6 +56,7 @@ pub struct ActiveThread {
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
|
text_thread_store: Entity<TextThreadStore>,
|
||||||
thread: Entity<Thread>,
|
thread: Entity<Thread>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
save_thread_task: Option<Task<()>>,
|
save_thread_task: Option<Task<()>>,
|
||||||
|
@ -719,6 +720,15 @@ fn open_markdown_link(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
Some(MentionLink::TextThread(path)) => workspace.update(cx, |workspace, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel
|
||||||
|
.open_saved_prompt_editor(path, window, cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
Some(MentionLink::Fetch(url)) => cx.open_url(&url),
|
Some(MentionLink::Fetch(url)) => cx.open_url(&url),
|
||||||
Some(MentionLink::Rule(prompt_id)) => window.dispatch_action(
|
Some(MentionLink::Rule(prompt_id)) => window.dispatch_action(
|
||||||
Box::new(OpenRulesLibrary {
|
Box::new(OpenRulesLibrary {
|
||||||
|
@ -743,6 +753,7 @@ impl ActiveThread {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread: Entity<Thread>,
|
thread: Entity<Thread>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
|
text_thread_store: Entity<TextThreadStore>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
@ -765,6 +776,7 @@ impl ActiveThread {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
language_registry,
|
language_registry,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
context_store,
|
context_store,
|
||||||
thread: thread.clone(),
|
thread: thread.clone(),
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -844,6 +856,14 @@ impl ActiveThread {
|
||||||
.map(|(id, state)| (*id, state.last_estimated_token_count.unwrap_or(0)))
|
.map(|(id, state)| (*id, state.last_estimated_token_count.unwrap_or(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn thread_store(&self) -> &Entity<ThreadStore> {
|
||||||
|
&self.thread_store
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_thread_store(&self) -> &Entity<TextThreadStore> {
|
||||||
|
&self.text_thread_store
|
||||||
|
}
|
||||||
|
|
||||||
fn push_message(
|
fn push_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &MessageId,
|
id: &MessageId,
|
||||||
|
@ -1264,6 +1284,7 @@ impl ActiveThread {
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.context_store.downgrade(),
|
self.context_store.downgrade(),
|
||||||
self.thread_store.downgrade(),
|
self.thread_store.downgrade(),
|
||||||
|
self.text_thread_store.downgrade(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -1285,6 +1306,7 @@ impl ActiveThread {
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
Some(self.thread_store.downgrade()),
|
Some(self.thread_store.downgrade()),
|
||||||
|
Some(self.text_thread_store.downgrade()),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::File,
|
SuggestContextKind::File,
|
||||||
window,
|
window,
|
||||||
|
@ -3439,14 +3461,21 @@ pub(crate) fn open_context(
|
||||||
AgentContextHandle::Thread(thread_context) => workspace.update(cx, |workspace, cx| {
|
AgentContextHandle::Thread(thread_context) => workspace.update(cx, |workspace, cx| {
|
||||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
panel.update(cx, |panel, cx| {
|
panel.update(cx, |panel, cx| {
|
||||||
let thread_id = thread_context.thread.read(cx).id().clone();
|
panel.open_thread(thread_context.thread.clone(), window, cx);
|
||||||
panel
|
|
||||||
.open_thread_by_id(&thread_id, window, cx)
|
|
||||||
.detach_and_log_err(cx)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
AgentContextHandle::TextThread(text_thread_context) => {
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel.open_prompt_editor(text_thread_context.context.clone(), window, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
AgentContextHandle::Rules(rules_context) => window.dispatch_action(
|
AgentContextHandle::Rules(rules_context) => window.dispatch_action(
|
||||||
Box::new(OpenRulesLibrary {
|
Box::new(OpenRulesLibrary {
|
||||||
prompt_to_select: Some(rules_context.prompt_id.0),
|
prompt_to_select: Some(rules_context.prompt_id.0),
|
||||||
|
@ -3585,18 +3614,25 @@ mod tests {
|
||||||
let (workspace, cx) =
|
let (workspace, cx) =
|
||||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
|
||||||
|
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
|
||||||
let thread_store = cx
|
let thread_store = cx
|
||||||
.update(|_, cx| {
|
.update(|_, cx| {
|
||||||
ThreadStore::load(
|
ThreadStore::load(
|
||||||
project.clone(),
|
project.clone(),
|
||||||
cx.new(|_| ToolWorkingSet::default()),
|
cx.new(|_| ToolWorkingSet::default()),
|
||||||
None,
|
None,
|
||||||
Arc::new(PromptBuilder::new(None).unwrap()),
|
prompt_builder.clone(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let text_thread_store = cx
|
||||||
|
.update(|_, cx| {
|
||||||
|
TextThreadStore::new(project.clone(), prompt_builder, Default::default(), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
|
||||||
let context_store = cx.new(|_cx| ContextStore::new(project.downgrade(), None));
|
let context_store = cx.new(|_cx| ContextStore::new(project.downgrade(), None));
|
||||||
|
@ -3612,6 +3648,7 @@ mod tests {
|
||||||
ActiveThread::new(
|
ActiveThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
language_registry.clone(),
|
language_registry.clone(),
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub use crate::assistant_panel::{AssistantPanel, ConcreteAssistantPanelDelegate}
|
||||||
pub use crate::context::{ContextLoadResult, LoadedContext};
|
pub use crate::context::{ContextLoadResult, LoadedContext};
|
||||||
pub use crate::inline_assistant::InlineAssistant;
|
pub use crate::inline_assistant::InlineAssistant;
|
||||||
pub use crate::thread::{Message, MessageSegment, Thread, ThreadEvent};
|
pub use crate::thread::{Message, MessageSegment, Thread, ThreadEvent};
|
||||||
pub use crate::thread_store::ThreadStore;
|
pub use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
|
||||||
pub use context_store::ContextStore;
|
pub use context_store::ContextStore;
|
||||||
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
pub use ui::preview::{all_agent_previews, get_agent_preview};
|
||||||
|
|
|
@ -52,7 +52,7 @@ use crate::history_store::{HistoryEntry, HistoryStore, RecentEntry};
|
||||||
use crate::message_editor::{MessageEditor, MessageEditorEvent};
|
use crate::message_editor::{MessageEditor, MessageEditorEvent};
|
||||||
use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio};
|
use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio};
|
||||||
use crate::thread_history::{PastContext, PastThread, ThreadHistory};
|
use crate::thread_history::{PastContext, PastThread, ThreadHistory};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
use crate::{
|
use crate::{
|
||||||
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, ExpandMessageEditor, Follow,
|
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, ExpandMessageEditor, Follow,
|
||||||
InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
|
InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
|
||||||
|
@ -313,7 +313,7 @@ pub struct AssistantPanel {
|
||||||
message_editor: Entity<MessageEditor>,
|
message_editor: Entity<MessageEditor>,
|
||||||
_active_thread_subscriptions: Vec<Subscription>,
|
_active_thread_subscriptions: Vec<Subscription>,
|
||||||
_default_model_subscription: Subscription,
|
_default_model_subscription: Subscription,
|
||||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
context_store: Entity<TextThreadStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
configuration: Option<Entity<AssistantConfiguration>>,
|
configuration: Option<Entity<AssistantConfiguration>>,
|
||||||
configuration_subscription: Option<Subscription>,
|
configuration_subscription: Option<Subscription>,
|
||||||
|
@ -419,7 +419,7 @@ impl AssistantPanel {
|
||||||
fn new(
|
fn new(
|
||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
context_store: Entity<TextThreadStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -447,6 +447,7 @@ impl AssistantPanel {
|
||||||
message_editor_context_store.clone(),
|
message_editor_context_store.clone(),
|
||||||
prompt_store.clone(),
|
prompt_store.clone(),
|
||||||
thread_store.downgrade(),
|
thread_store.downgrade(),
|
||||||
|
context_store.downgrade(),
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -483,6 +484,7 @@ impl AssistantPanel {
|
||||||
ActiveThread::new(
|
ActiveThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
context_store.clone(),
|
||||||
message_editor_context_store.clone(),
|
message_editor_context_store.clone(),
|
||||||
language_registry.clone(),
|
language_registry.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
@ -676,6 +678,10 @@ impl AssistantPanel {
|
||||||
&self.thread_store
|
&self.thread_store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn text_thread_store(&self) -> &Entity<TextThreadStore> {
|
||||||
|
&self.context_store
|
||||||
|
}
|
||||||
|
|
||||||
fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.thread
|
self.thread
|
||||||
.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
|
.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
|
||||||
|
@ -727,6 +733,7 @@ impl AssistantPanel {
|
||||||
ActiveThread::new(
|
ActiveThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
self.thread_store.clone(),
|
self.thread_store.clone(),
|
||||||
|
self.context_store.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
self.language_registry.clone(),
|
self.language_registry.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
|
@ -751,6 +758,7 @@ impl AssistantPanel {
|
||||||
context_store,
|
context_store,
|
||||||
self.prompt_store.clone(),
|
self.prompt_store.clone(),
|
||||||
self.thread_store.downgrade(),
|
self.thread_store.downgrade(),
|
||||||
|
self.context_store.downgrade(),
|
||||||
thread,
|
thread,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -854,44 +862,41 @@ impl AssistantPanel {
|
||||||
let context = self
|
let context = self
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |store, cx| store.open_local_context(path, cx));
|
.update(cx, |store, cx| store.open_local_context(path, cx));
|
||||||
let fs = self.fs.clone();
|
|
||||||
let project = self.project.clone();
|
|
||||||
let workspace = self.workspace.clone();
|
|
||||||
|
|
||||||
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
|
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let context = context.await?;
|
let context = context.await?;
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
let editor = cx.new(|cx| {
|
this.open_prompt_editor(context, window, cx);
|
||||||
ContextEditor::for_context(
|
})
|
||||||
context,
|
|
||||||
fs,
|
|
||||||
workspace,
|
|
||||||
project,
|
|
||||||
lsp_adapter_delegate,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set_active_view(
|
|
||||||
ActiveView::prompt_editor(
|
|
||||||
editor.clone(),
|
|
||||||
this.language_registry.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})??;
|
|
||||||
Ok(())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn open_prompt_editor(
|
||||||
|
&mut self,
|
||||||
|
context: Entity<AssistantContext>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project.clone(), cx)
|
||||||
|
.log_err()
|
||||||
|
.flatten();
|
||||||
|
let editor = cx.new(|cx| {
|
||||||
|
ContextEditor::for_context(
|
||||||
|
context,
|
||||||
|
self.fs.clone(),
|
||||||
|
self.workspace.clone(),
|
||||||
|
self.project.clone(),
|
||||||
|
lsp_adapter_delegate,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
self.set_active_view(
|
||||||
|
ActiveView::prompt_editor(editor.clone(), self.language_registry.clone(), window, cx),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn open_thread_by_id(
|
pub(crate) fn open_thread_by_id(
|
||||||
&mut self,
|
&mut self,
|
||||||
thread_id: &ThreadId,
|
thread_id: &ThreadId,
|
||||||
|
@ -936,6 +941,7 @@ impl AssistantPanel {
|
||||||
ActiveThread::new(
|
ActiveThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
self.thread_store.clone(),
|
self.thread_store.clone(),
|
||||||
|
self.context_store.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
self.language_registry.clone(),
|
self.language_registry.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
|
@ -960,6 +966,7 @@ impl AssistantPanel {
|
||||||
context_store,
|
context_store,
|
||||||
self.prompt_store.clone(),
|
self.prompt_store.clone(),
|
||||||
self.thread_store.downgrade(),
|
self.thread_store.downgrade(),
|
||||||
|
self.context_store.downgrade(),
|
||||||
thread,
|
thread,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -1067,7 +1074,9 @@ impl AssistantPanel {
|
||||||
.app_state()
|
.app_state()
|
||||||
.languages
|
.languages
|
||||||
.language_for_name("Markdown");
|
.language_for_name("Markdown");
|
||||||
let thread = self.active_thread(cx);
|
let Some(thread) = self.active_thread() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
cx.spawn_in(window, async move |_this, cx| {
|
cx.spawn_in(window, async move |_this, cx| {
|
||||||
let markdown_language = markdown_language_task.await?;
|
let markdown_language = markdown_language_task.await?;
|
||||||
|
|
||||||
|
@ -1133,8 +1142,11 @@ impl AssistantPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn active_thread(&self, cx: &App) -> Entity<Thread> {
|
pub(crate) fn active_thread(&self) -> Option<Entity<Thread>> {
|
||||||
self.thread.read(cx).thread().clone()
|
match &self.active_view {
|
||||||
|
ActiveView::Thread { thread, .. } => thread.upgrade(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn delete_thread(
|
pub(crate) fn delete_thread(
|
||||||
|
@ -2423,12 +2435,14 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||||
};
|
};
|
||||||
let prompt_store = None;
|
let prompt_store = None;
|
||||||
let thread_store = None;
|
let thread_store = None;
|
||||||
|
let text_thread_store = None;
|
||||||
assistant.assist(
|
assistant.assist(
|
||||||
&prompt_editor,
|
&prompt_editor,
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
project,
|
project,
|
||||||
prompt_store,
|
prompt_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::hash::{Hash, Hasher};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{ops::Range, path::Path, sync::Arc};
|
use std::{ops::Range, path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use assistant_context_editor::AssistantContext;
|
||||||
use assistant_tool::outline;
|
use assistant_tool::outline;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::display_map::CreaseId;
|
use editor::display_map::CreaseId;
|
||||||
|
@ -33,6 +34,7 @@ pub enum ContextKind {
|
||||||
Selection,
|
Selection,
|
||||||
FetchedUrl,
|
FetchedUrl,
|
||||||
Thread,
|
Thread,
|
||||||
|
TextThread,
|
||||||
Rules,
|
Rules,
|
||||||
Image,
|
Image,
|
||||||
}
|
}
|
||||||
|
@ -46,6 +48,7 @@ impl ContextKind {
|
||||||
ContextKind::Selection => IconName::Context,
|
ContextKind::Selection => IconName::Context,
|
||||||
ContextKind::FetchedUrl => IconName::Globe,
|
ContextKind::FetchedUrl => IconName::Globe,
|
||||||
ContextKind::Thread => IconName::MessageBubbles,
|
ContextKind::Thread => IconName::MessageBubbles,
|
||||||
|
ContextKind::TextThread => IconName::MessageBubbles,
|
||||||
ContextKind::Rules => RULES_ICON,
|
ContextKind::Rules => RULES_ICON,
|
||||||
ContextKind::Image => IconName::Image,
|
ContextKind::Image => IconName::Image,
|
||||||
}
|
}
|
||||||
|
@ -65,6 +68,7 @@ pub enum AgentContextHandle {
|
||||||
Selection(SelectionContextHandle),
|
Selection(SelectionContextHandle),
|
||||||
FetchedUrl(FetchedUrlContext),
|
FetchedUrl(FetchedUrlContext),
|
||||||
Thread(ThreadContextHandle),
|
Thread(ThreadContextHandle),
|
||||||
|
TextThread(TextThreadContextHandle),
|
||||||
Rules(RulesContextHandle),
|
Rules(RulesContextHandle),
|
||||||
Image(ImageContext),
|
Image(ImageContext),
|
||||||
}
|
}
|
||||||
|
@ -78,6 +82,7 @@ impl AgentContextHandle {
|
||||||
Self::Selection(context) => context.context_id,
|
Self::Selection(context) => context.context_id,
|
||||||
Self::FetchedUrl(context) => context.context_id,
|
Self::FetchedUrl(context) => context.context_id,
|
||||||
Self::Thread(context) => context.context_id,
|
Self::Thread(context) => context.context_id,
|
||||||
|
Self::TextThread(context) => context.context_id,
|
||||||
Self::Rules(context) => context.context_id,
|
Self::Rules(context) => context.context_id,
|
||||||
Self::Image(context) => context.context_id,
|
Self::Image(context) => context.context_id,
|
||||||
}
|
}
|
||||||
|
@ -98,6 +103,7 @@ pub enum AgentContext {
|
||||||
Selection(SelectionContext),
|
Selection(SelectionContext),
|
||||||
FetchedUrl(FetchedUrlContext),
|
FetchedUrl(FetchedUrlContext),
|
||||||
Thread(ThreadContext),
|
Thread(ThreadContext),
|
||||||
|
TextThread(TextThreadContext),
|
||||||
Rules(RulesContext),
|
Rules(RulesContext),
|
||||||
Image(ImageContext),
|
Image(ImageContext),
|
||||||
}
|
}
|
||||||
|
@ -115,6 +121,9 @@ impl AgentContext {
|
||||||
}
|
}
|
||||||
AgentContext::FetchedUrl(context) => AgentContextHandle::FetchedUrl(context.clone()),
|
AgentContext::FetchedUrl(context) => AgentContextHandle::FetchedUrl(context.clone()),
|
||||||
AgentContext::Thread(context) => AgentContextHandle::Thread(context.handle.clone()),
|
AgentContext::Thread(context) => AgentContextHandle::Thread(context.handle.clone()),
|
||||||
|
AgentContext::TextThread(context) => {
|
||||||
|
AgentContextHandle::TextThread(context.handle.clone())
|
||||||
|
}
|
||||||
AgentContext::Rules(context) => AgentContextHandle::Rules(context.handle.clone()),
|
AgentContext::Rules(context) => AgentContextHandle::Rules(context.handle.clone()),
|
||||||
AgentContext::Image(context) => AgentContextHandle::Image(context.clone()),
|
AgentContext::Image(context) => AgentContextHandle::Image(context.clone()),
|
||||||
}
|
}
|
||||||
|
@ -609,6 +618,54 @@ impl Display for ThreadContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TextThreadContextHandle {
|
||||||
|
pub context: Entity<AssistantContext>,
|
||||||
|
pub context_id: ContextId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TextThreadContext {
|
||||||
|
pub handle: TextThreadContextHandle,
|
||||||
|
pub title: SharedString,
|
||||||
|
pub text: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextThreadContextHandle {
|
||||||
|
// pub fn lookup_key() ->
|
||||||
|
pub fn eq_for_key(&self, other: &Self) -> bool {
|
||||||
|
self.context == other.context
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_for_key<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.context.hash(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self, cx: &App) -> SharedString {
|
||||||
|
self.context.read(cx).summary_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(self, cx: &App) -> Task<Option<(AgentContext, Vec<Entity<Buffer>>)>> {
|
||||||
|
let title = self.title(cx);
|
||||||
|
let text = self.context.read(cx).to_xml(cx);
|
||||||
|
let context = AgentContext::TextThread(TextThreadContext {
|
||||||
|
title,
|
||||||
|
text: text.into(),
|
||||||
|
handle: self,
|
||||||
|
});
|
||||||
|
Task::ready(Some((context, vec![])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TextThreadContext {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
// TODO: escape title?
|
||||||
|
write!(f, "<text_thread title=\"{}\">\n", self.title)?;
|
||||||
|
write!(f, "{}", self.text.trim())?;
|
||||||
|
write!(f, "\n</text_thread>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RulesContextHandle {
|
pub struct RulesContextHandle {
|
||||||
pub prompt_id: UserPromptId,
|
pub prompt_id: UserPromptId,
|
||||||
|
@ -785,6 +842,7 @@ pub fn load_context(
|
||||||
AgentContextHandle::Selection(context) => load_tasks.push(context.load(cx)),
|
AgentContextHandle::Selection(context) => load_tasks.push(context.load(cx)),
|
||||||
AgentContextHandle::FetchedUrl(context) => load_tasks.push(context.load()),
|
AgentContextHandle::FetchedUrl(context) => load_tasks.push(context.load()),
|
||||||
AgentContextHandle::Thread(context) => load_tasks.push(context.load(cx)),
|
AgentContextHandle::Thread(context) => load_tasks.push(context.load(cx)),
|
||||||
|
AgentContextHandle::TextThread(context) => load_tasks.push(context.load(cx)),
|
||||||
AgentContextHandle::Rules(context) => load_tasks.push(context.load(prompt_store, cx)),
|
AgentContextHandle::Rules(context) => load_tasks.push(context.load(prompt_store, cx)),
|
||||||
AgentContextHandle::Image(context) => load_tasks.push(context.load(cx)),
|
AgentContextHandle::Image(context) => load_tasks.push(context.load(cx)),
|
||||||
}
|
}
|
||||||
|
@ -810,6 +868,7 @@ pub fn load_context(
|
||||||
let mut selection_context = Vec::new();
|
let mut selection_context = Vec::new();
|
||||||
let mut fetched_url_context = Vec::new();
|
let mut fetched_url_context = Vec::new();
|
||||||
let mut thread_context = Vec::new();
|
let mut thread_context = Vec::new();
|
||||||
|
let mut text_thread_context = Vec::new();
|
||||||
let mut rules_context = Vec::new();
|
let mut rules_context = Vec::new();
|
||||||
let mut images = Vec::new();
|
let mut images = Vec::new();
|
||||||
for context in &contexts {
|
for context in &contexts {
|
||||||
|
@ -820,17 +879,21 @@ pub fn load_context(
|
||||||
AgentContext::Selection(context) => selection_context.push(context),
|
AgentContext::Selection(context) => selection_context.push(context),
|
||||||
AgentContext::FetchedUrl(context) => fetched_url_context.push(context),
|
AgentContext::FetchedUrl(context) => fetched_url_context.push(context),
|
||||||
AgentContext::Thread(context) => thread_context.push(context),
|
AgentContext::Thread(context) => thread_context.push(context),
|
||||||
|
AgentContext::TextThread(context) => text_thread_context.push(context),
|
||||||
AgentContext::Rules(context) => rules_context.push(context),
|
AgentContext::Rules(context) => rules_context.push(context),
|
||||||
AgentContext::Image(context) => images.extend(context.image()),
|
AgentContext::Image(context) => images.extend(context.image()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use empty text if there are no contexts that contribute to text (everything but image
|
||||||
|
// context).
|
||||||
if file_context.is_empty()
|
if file_context.is_empty()
|
||||||
&& directory_context.is_empty()
|
&& directory_context.is_empty()
|
||||||
&& symbol_context.is_empty()
|
&& symbol_context.is_empty()
|
||||||
&& selection_context.is_empty()
|
&& selection_context.is_empty()
|
||||||
&& fetched_url_context.is_empty()
|
&& fetched_url_context.is_empty()
|
||||||
&& thread_context.is_empty()
|
&& thread_context.is_empty()
|
||||||
|
&& text_thread_context.is_empty()
|
||||||
&& rules_context.is_empty()
|
&& rules_context.is_empty()
|
||||||
{
|
{
|
||||||
return ContextLoadResult {
|
return ContextLoadResult {
|
||||||
|
@ -903,6 +966,15 @@ pub fn load_context(
|
||||||
text.push_str("</conversation_threads>\n");
|
text.push_str("</conversation_threads>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !text_thread_context.is_empty() {
|
||||||
|
text.push_str("<text_threads>");
|
||||||
|
for context in text_thread_context {
|
||||||
|
text.push('\n');
|
||||||
|
let _ = writeln!(text, "{context}");
|
||||||
|
}
|
||||||
|
text.push_str("<text_threads>");
|
||||||
|
}
|
||||||
|
|
||||||
if !rules_context.is_empty() {
|
if !rules_context.is_empty() {
|
||||||
text.push_str(
|
text.push_str(
|
||||||
"<user_rules>\n\
|
"<user_rules>\n\
|
||||||
|
@ -1019,6 +1091,11 @@ impl PartialEq for AgentContextKey {
|
||||||
return context.eq_for_key(other_context);
|
return context.eq_for_key(other_context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AgentContextHandle::TextThread(context) => {
|
||||||
|
if let AgentContextHandle::TextThread(other_context) = &other.0 {
|
||||||
|
return context.eq_for_key(other_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -1033,6 +1110,7 @@ impl Hash for AgentContextKey {
|
||||||
AgentContextHandle::Selection(context) => context.hash_for_key(state),
|
AgentContextHandle::Selection(context) => context.hash_for_key(state),
|
||||||
AgentContextHandle::FetchedUrl(context) => context.hash_for_key(state),
|
AgentContextHandle::FetchedUrl(context) => context.hash_for_key(state),
|
||||||
AgentContextHandle::Thread(context) => context.hash_for_key(state),
|
AgentContextHandle::Thread(context) => context.hash_for_key(state),
|
||||||
|
AgentContextHandle::TextThread(context) => context.hash_for_key(state),
|
||||||
AgentContextHandle::Rules(context) => context.hash_for_key(state),
|
AgentContextHandle::Rules(context) => context.hash_for_key(state),
|
||||||
AgentContextHandle::Image(context) => context.hash_for_key(state),
|
AgentContextHandle::Image(context) => context.hash_for_key(state),
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod symbol_context_picker;
|
||||||
mod thread_context_picker;
|
mod thread_context_picker;
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
|
@ -22,11 +22,14 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
|
use paths::contexts_dir;
|
||||||
use project::{Entry, ProjectPath};
|
use project::{Entry, ProjectPath};
|
||||||
use prompt_store::{PromptStore, UserPromptId};
|
use prompt_store::{PromptStore, UserPromptId};
|
||||||
use rules_context_picker::{RulesContextEntry, RulesContextPicker};
|
use rules_context_picker::{RulesContextEntry, RulesContextPicker};
|
||||||
use symbol_context_picker::SymbolContextPicker;
|
use symbol_context_picker::SymbolContextPicker;
|
||||||
use thread_context_picker::{ThreadContextEntry, ThreadContextPicker, render_thread_context_entry};
|
use thread_context_picker::{
|
||||||
|
ThreadContextEntry, ThreadContextPicker, render_thread_context_entry, unordered_thread_entries,
|
||||||
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
|
ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
|
||||||
};
|
};
|
||||||
|
@ -37,7 +40,7 @@ use crate::AssistantPanel;
|
||||||
use crate::context::RULES_ICON;
|
use crate::context::RULES_ICON;
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::thread::ThreadId;
|
use crate::thread::ThreadId;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum ContextPickerEntry {
|
enum ContextPickerEntry {
|
||||||
|
@ -164,6 +167,7 @@ pub(super) struct ContextPicker {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
@ -172,6 +176,7 @@ impl ContextPicker {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -208,6 +213,7 @@ impl ContextPicker {
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
prompt_store,
|
prompt_store,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
|
@ -340,10 +346,15 @@ impl ContextPicker {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ContextPickerMode::Thread => {
|
ContextPickerMode::Thread => {
|
||||||
if let Some(thread_store) = self.thread_store.as_ref() {
|
if let Some((thread_store, text_thread_store)) = self
|
||||||
|
.thread_store
|
||||||
|
.as_ref()
|
||||||
|
.zip(self.text_thread_store.as_ref())
|
||||||
|
{
|
||||||
self.mode = ContextPickerState::Thread(cx.new(|cx| {
|
self.mode = ContextPickerState::Thread(cx.new(|cx| {
|
||||||
ThreadContextPicker::new(
|
ThreadContextPicker::new(
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
window,
|
window,
|
||||||
|
@ -447,30 +458,53 @@ impl ContextPicker {
|
||||||
|
|
||||||
fn add_recent_thread(
|
fn add_recent_thread(
|
||||||
&self,
|
&self,
|
||||||
thread: ThreadContextEntry,
|
entry: ThreadContextEntry,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let Some(context_store) = self.context_store.upgrade() else {
|
let Some(context_store) = self.context_store.upgrade() else {
|
||||||
return Task::ready(Err(anyhow!("context store not available")));
|
return Task::ready(Err(anyhow!("context store not available")));
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(thread_store) = self
|
match entry {
|
||||||
.thread_store
|
ThreadContextEntry::Thread { id, .. } => {
|
||||||
.as_ref()
|
let Some(thread_store) = self
|
||||||
.and_then(|thread_store| thread_store.upgrade())
|
.thread_store
|
||||||
else {
|
.as_ref()
|
||||||
return Task::ready(Err(anyhow!("thread store not available")));
|
.and_then(|thread_store| thread_store.upgrade())
|
||||||
};
|
else {
|
||||||
|
return Task::ready(Err(anyhow!("thread store not available")));
|
||||||
|
};
|
||||||
|
|
||||||
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&thread.id, cx));
|
let open_thread_task =
|
||||||
cx.spawn(async move |this, cx| {
|
thread_store.update(cx, |this, cx| this.open_thread(&id, cx));
|
||||||
let thread = open_thread_task.await?;
|
cx.spawn(async move |this, cx| {
|
||||||
context_store.update(cx, |context_store, cx| {
|
let thread = open_thread_task.await?;
|
||||||
context_store.add_thread(thread, true, cx);
|
context_store.update(cx, |context_store, cx| {
|
||||||
})?;
|
context_store.add_thread(thread, true, cx);
|
||||||
|
})?;
|
||||||
|
this.update(cx, |_this, cx| cx.notify())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ThreadContextEntry::Context { path, .. } => {
|
||||||
|
let Some(text_thread_store) = self
|
||||||
|
.text_thread_store
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|thread_store| thread_store.upgrade())
|
||||||
|
else {
|
||||||
|
return Task::ready(Err(anyhow!("text thread store not available")));
|
||||||
|
};
|
||||||
|
|
||||||
this.update(cx, |_this, cx| cx.notify())
|
let task = text_thread_store
|
||||||
})
|
.update(cx, |this, cx| this.open_local_context(path.clone(), cx));
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let thread = task.await?;
|
||||||
|
context_store.update(cx, |context_store, cx| {
|
||||||
|
context_store.add_text_thread(thread, true, cx);
|
||||||
|
})?;
|
||||||
|
this.update(cx, |_this, cx| cx.notify())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recent_entries(&self, cx: &mut App) -> Vec<RecentEntry> {
|
fn recent_entries(&self, cx: &mut App) -> Vec<RecentEntry> {
|
||||||
|
@ -485,6 +519,7 @@ impl ContextPicker {
|
||||||
recent_context_picker_entries(
|
recent_context_picker_entries(
|
||||||
context_store,
|
context_store,
|
||||||
self.thread_store.clone(),
|
self.thread_store.clone(),
|
||||||
|
self.text_thread_store.clone(),
|
||||||
workspace,
|
workspace,
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
|
@ -583,6 +618,7 @@ fn available_context_picker_entries(
|
||||||
fn recent_context_picker_entries(
|
fn recent_context_picker_entries(
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
workspace: Entity<Workspace>,
|
workspace: Entity<Workspace>,
|
||||||
exclude_path: Option<ProjectPath>,
|
exclude_path: Option<ProjectPath>,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
|
@ -612,24 +648,34 @@ fn recent_context_picker_entries(
|
||||||
|
|
||||||
let active_thread_id = workspace
|
let active_thread_id = workspace
|
||||||
.panel::<AssistantPanel>(cx)
|
.panel::<AssistantPanel>(cx)
|
||||||
.map(|panel| panel.read(cx).active_thread(cx).read(cx).id());
|
.and_then(|panel| Some(panel.read(cx).active_thread()?.read(cx).id()));
|
||||||
|
|
||||||
|
if let Some((thread_store, text_thread_store)) = thread_store
|
||||||
|
.and_then(|store| store.upgrade())
|
||||||
|
.zip(text_thread_store.and_then(|store| store.upgrade()))
|
||||||
|
{
|
||||||
|
let mut threads = unordered_thread_entries(thread_store, text_thread_store, cx)
|
||||||
|
.filter(|(_, thread)| match thread {
|
||||||
|
ThreadContextEntry::Thread { id, .. } => {
|
||||||
|
Some(id) != active_thread_id && !current_threads.contains(id)
|
||||||
|
}
|
||||||
|
ThreadContextEntry::Context { .. } => true,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
const RECENT_COUNT: usize = 2;
|
||||||
|
if threads.len() > RECENT_COUNT {
|
||||||
|
threads.select_nth_unstable_by_key(RECENT_COUNT - 1, |(updated_at, _)| {
|
||||||
|
std::cmp::Reverse(*updated_at)
|
||||||
|
});
|
||||||
|
threads.truncate(RECENT_COUNT);
|
||||||
|
}
|
||||||
|
threads.sort_unstable_by_key(|(updated_at, _)| std::cmp::Reverse(*updated_at));
|
||||||
|
|
||||||
if let Some(thread_store) = thread_store.and_then(|thread_store| thread_store.upgrade()) {
|
|
||||||
recent.extend(
|
recent.extend(
|
||||||
thread_store
|
threads
|
||||||
.read(cx)
|
|
||||||
.reverse_chronological_threads()
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|thread| {
|
.map(|(_, thread)| RecentEntry::Thread(thread)),
|
||||||
Some(&thread.id) != active_thread_id && !current_threads.contains(&thread.id)
|
|
||||||
})
|
|
||||||
.take(2)
|
|
||||||
.map(|thread| {
|
|
||||||
RecentEntry::Thread(ThreadContextEntry {
|
|
||||||
id: thread.id,
|
|
||||||
summary: thread.summary,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -827,6 +873,7 @@ pub enum MentionLink {
|
||||||
Selection(ProjectPath, Range<usize>),
|
Selection(ProjectPath, Range<usize>),
|
||||||
Fetch(String),
|
Fetch(String),
|
||||||
Thread(ThreadId),
|
Thread(ThreadId),
|
||||||
|
TextThread(Arc<Path>),
|
||||||
Rule(UserPromptId),
|
Rule(UserPromptId),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -838,6 +885,8 @@ impl MentionLink {
|
||||||
const FETCH: &str = "@fetch";
|
const FETCH: &str = "@fetch";
|
||||||
const RULE: &str = "@rule";
|
const RULE: &str = "@rule";
|
||||||
|
|
||||||
|
const TEXT_THREAD_URL_PREFIX: &str = "text-thread://";
|
||||||
|
|
||||||
const SEPARATOR: &str = ":";
|
const SEPARATOR: &str = ":";
|
||||||
|
|
||||||
pub fn is_valid(url: &str) -> bool {
|
pub fn is_valid(url: &str) -> bool {
|
||||||
|
@ -877,7 +926,22 @@ impl MentionLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_thread(thread: &ThreadContextEntry) -> String {
|
pub fn for_thread(thread: &ThreadContextEntry) -> String {
|
||||||
format!("[@{}]({}:{})", thread.summary, Self::THREAD, thread.id)
|
match thread {
|
||||||
|
ThreadContextEntry::Thread { id, title } => {
|
||||||
|
format!("[@{}]({}:{})", title, Self::THREAD, id)
|
||||||
|
}
|
||||||
|
ThreadContextEntry::Context { path, title } => {
|
||||||
|
let filename = path.file_name().unwrap_or_default();
|
||||||
|
let escaped_filename = urlencoding::encode(&filename.to_string_lossy()).to_string();
|
||||||
|
format!(
|
||||||
|
"[@{}]({}:{}{})",
|
||||||
|
title,
|
||||||
|
Self::THREAD,
|
||||||
|
Self::TEXT_THREAD_URL_PREFIX,
|
||||||
|
escaped_filename
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_fetch(url: &str) -> String {
|
pub fn for_fetch(url: &str) -> String {
|
||||||
|
@ -939,8 +1003,15 @@ impl MentionLink {
|
||||||
Some(MentionLink::Selection(project_path, line_range))
|
Some(MentionLink::Selection(project_path, line_range))
|
||||||
}
|
}
|
||||||
Self::THREAD => {
|
Self::THREAD => {
|
||||||
let thread_id = ThreadId::from(argument);
|
if let Some(encoded_filename) = argument.strip_prefix(Self::TEXT_THREAD_URL_PREFIX)
|
||||||
Some(MentionLink::Thread(thread_id))
|
{
|
||||||
|
let filename = urlencoding::decode(encoded_filename).ok()?;
|
||||||
|
let path = contexts_dir().join(filename.as_ref()).into();
|
||||||
|
Some(MentionLink::TextThread(path))
|
||||||
|
} else {
|
||||||
|
let thread_id = ThreadId::from(argument);
|
||||||
|
Some(MentionLink::Thread(thread_id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::FETCH => Some(MentionLink::Fetch(argument.to_string())),
|
Self::FETCH => Some(MentionLink::Fetch(argument.to_string())),
|
||||||
Self::RULE => {
|
Self::RULE => {
|
||||||
|
|
|
@ -25,7 +25,7 @@ use workspace::Workspace;
|
||||||
use crate::Thread;
|
use crate::Thread;
|
||||||
use crate::context::{AgentContextHandle, AgentContextKey, ContextCreasesAddon, RULES_ICON};
|
use crate::context::{AgentContextHandle, AgentContextKey, ContextCreasesAddon, RULES_ICON};
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
|
|
||||||
use super::fetch_context_picker::fetch_url_content;
|
use super::fetch_context_picker::fetch_url_content;
|
||||||
use super::file_context_picker::{FileMatch, search_files};
|
use super::file_context_picker::{FileMatch, search_files};
|
||||||
|
@ -72,6 +72,7 @@ fn search(
|
||||||
recent_entries: Vec<RecentEntry>,
|
recent_entries: Vec<RecentEntry>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_context_store: Option<WeakEntity<assistant_context_editor::ContextStore>>,
|
||||||
workspace: Entity<Workspace>,
|
workspace: Entity<Workspace>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Vec<Match>> {
|
) -> Task<Vec<Match>> {
|
||||||
|
@ -101,9 +102,18 @@ fn search(
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ContextPickerMode::Thread) => {
|
Some(ContextPickerMode::Thread) => {
|
||||||
if let Some(thread_store) = thread_store.as_ref().and_then(|t| t.upgrade()) {
|
if let Some((thread_store, context_store)) = thread_store
|
||||||
let search_threads_task =
|
.as_ref()
|
||||||
search_threads(query.clone(), cancellation_flag.clone(), thread_store, cx);
|
.and_then(|t| t.upgrade())
|
||||||
|
.zip(text_thread_context_store.as_ref().and_then(|t| t.upgrade()))
|
||||||
|
{
|
||||||
|
let search_threads_task = search_threads(
|
||||||
|
query.clone(),
|
||||||
|
cancellation_flag.clone(),
|
||||||
|
thread_store,
|
||||||
|
context_store,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
search_threads_task
|
search_threads_task
|
||||||
.await
|
.await
|
||||||
|
@ -236,6 +246,7 @@ pub struct ContextPickerCompletionProvider {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakEntity<Editor>,
|
||||||
excluded_buffer: Option<WeakEntity<Buffer>>,
|
excluded_buffer: Option<WeakEntity<Buffer>>,
|
||||||
}
|
}
|
||||||
|
@ -245,6 +256,7 @@ impl ContextPickerCompletionProvider {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakEntity<Editor>,
|
||||||
exclude_buffer: Option<WeakEntity<Buffer>>,
|
exclude_buffer: Option<WeakEntity<Buffer>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -252,6 +264,7 @@ impl ContextPickerCompletionProvider {
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
editor,
|
editor,
|
||||||
excluded_buffer: exclude_buffer,
|
excluded_buffer: exclude_buffer,
|
||||||
}
|
}
|
||||||
|
@ -400,6 +413,7 @@ impl ContextPickerCompletionProvider {
|
||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
|
text_thread_store: Entity<TextThreadStore>,
|
||||||
) -> Completion {
|
) -> Completion {
|
||||||
let icon_for_completion = if recent {
|
let icon_for_completion = if recent {
|
||||||
IconName::HistoryRerun
|
IconName::HistoryRerun
|
||||||
|
@ -411,38 +425,58 @@ impl ContextPickerCompletionProvider {
|
||||||
Completion {
|
Completion {
|
||||||
replace_range: source_range.clone(),
|
replace_range: source_range.clone(),
|
||||||
new_text,
|
new_text,
|
||||||
label: CodeLabel::plain(thread_entry.summary.to_string(), None),
|
label: CodeLabel::plain(thread_entry.title().to_string(), None),
|
||||||
documentation: None,
|
documentation: None,
|
||||||
insert_text_mode: None,
|
insert_text_mode: None,
|
||||||
source: project::CompletionSource::Custom,
|
source: project::CompletionSource::Custom,
|
||||||
icon_path: Some(icon_for_completion.path().into()),
|
icon_path: Some(icon_for_completion.path().into()),
|
||||||
confirm: Some(confirm_completion_callback(
|
confirm: Some(confirm_completion_callback(
|
||||||
IconName::MessageBubbles.path().into(),
|
IconName::MessageBubbles.path().into(),
|
||||||
thread_entry.summary.clone(),
|
thread_entry.title().clone(),
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
source_range.start,
|
source_range.start,
|
||||||
new_text_len,
|
new_text_len,
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
move |cx| {
|
move |cx| match &thread_entry {
|
||||||
let thread_id = thread_entry.id.clone();
|
ThreadContextEntry::Thread { id, .. } => {
|
||||||
let context_store = context_store.clone();
|
let thread_id = id.clone();
|
||||||
let thread_store = thread_store.clone();
|
let context_store = context_store.clone();
|
||||||
cx.spawn::<_, Option<_>>(async move |cx| {
|
let thread_store = thread_store.clone();
|
||||||
let thread: Entity<Thread> = thread_store
|
cx.spawn::<_, Option<_>>(async move |cx| {
|
||||||
.update(cx, |thread_store, cx| {
|
let thread: Entity<Thread> = thread_store
|
||||||
thread_store.open_thread(&thread_id, cx)
|
.update(cx, |thread_store, cx| {
|
||||||
})
|
thread_store.open_thread(&thread_id, cx)
|
||||||
.ok()?
|
})
|
||||||
.await
|
.ok()?
|
||||||
.log_err()?;
|
.await
|
||||||
let context = context_store
|
.log_err()?;
|
||||||
.update(cx, |context_store, cx| {
|
let context = context_store
|
||||||
context_store.add_thread(thread, false, cx)
|
.update(cx, |context_store, cx| {
|
||||||
})
|
context_store.add_thread(thread, false, cx)
|
||||||
.ok()??;
|
})
|
||||||
Some(context)
|
.ok()??;
|
||||||
})
|
Some(context)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ThreadContextEntry::Context { path, .. } => {
|
||||||
|
let path = path.clone();
|
||||||
|
let context_store = context_store.clone();
|
||||||
|
let text_thread_store = text_thread_store.clone();
|
||||||
|
cx.spawn::<_, Option<_>>(async move |cx| {
|
||||||
|
let thread = text_thread_store
|
||||||
|
.update(cx, |store, cx| store.open_local_context(path, cx))
|
||||||
|
.ok()?
|
||||||
|
.await
|
||||||
|
.log_err()?;
|
||||||
|
let context = context_store
|
||||||
|
.update(cx, |context_store, cx| {
|
||||||
|
context_store.add_text_thread(thread, false, cx)
|
||||||
|
})
|
||||||
|
.ok()??;
|
||||||
|
Some(context)
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
@ -733,6 +767,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
..snapshot.anchor_before(state.source_range.end);
|
..snapshot.anchor_before(state.source_range.end);
|
||||||
|
|
||||||
let thread_store = self.thread_store.clone();
|
let thread_store = self.thread_store.clone();
|
||||||
|
let text_thread_store = self.text_thread_store.clone();
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let http_client = workspace.read(cx).client().http_client();
|
let http_client = workspace.read(cx).client().http_client();
|
||||||
|
|
||||||
|
@ -749,6 +784,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
let recent_entries = recent_context_picker_entries(
|
let recent_entries = recent_context_picker_entries(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
excluded_path.clone(),
|
excluded_path.clone(),
|
||||||
cx,
|
cx,
|
||||||
|
@ -768,6 +804,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
recent_entries,
|
recent_entries,
|
||||||
prompt_store,
|
prompt_store,
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -819,6 +856,8 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
thread, is_recent, ..
|
thread, is_recent, ..
|
||||||
}) => {
|
}) => {
|
||||||
let thread_store = thread_store.as_ref().and_then(|t| t.upgrade())?;
|
let thread_store = thread_store.as_ref().and_then(|t| t.upgrade())?;
|
||||||
|
let text_thread_store =
|
||||||
|
text_thread_store.as_ref().and_then(|t| t.upgrade())?;
|
||||||
Some(Self::completion_for_thread(
|
Some(Self::completion_for_thread(
|
||||||
thread,
|
thread,
|
||||||
excerpt_id,
|
excerpt_id,
|
||||||
|
@ -827,6 +866,7 @@ impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
editor.clone(),
|
editor.clone(),
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1247,6 +1287,7 @@ mod tests {
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
editor_entity,
|
editor_entity,
|
||||||
last_opened_buffer,
|
last_opened_buffer,
|
||||||
))));
|
))));
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
|
@ -9,7 +11,7 @@ use ui::{ListItem, prelude::*};
|
||||||
use crate::context_picker::ContextPicker;
|
use crate::context_picker::ContextPicker;
|
||||||
use crate::context_store::{self, ContextStore};
|
use crate::context_store::{self, ContextStore};
|
||||||
use crate::thread::ThreadId;
|
use crate::thread::ThreadId;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
|
|
||||||
pub struct ThreadContextPicker {
|
pub struct ThreadContextPicker {
|
||||||
picker: Entity<Picker<ThreadContextPickerDelegate>>,
|
picker: Entity<Picker<ThreadContextPickerDelegate>>,
|
||||||
|
@ -18,13 +20,18 @@ pub struct ThreadContextPicker {
|
||||||
impl ThreadContextPicker {
|
impl ThreadContextPicker {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
text_thread_context_store: WeakEntity<TextThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let delegate =
|
let delegate = ThreadContextPickerDelegate::new(
|
||||||
ThreadContextPickerDelegate::new(thread_store, context_picker, context_store);
|
thread_store,
|
||||||
|
text_thread_context_store,
|
||||||
|
context_picker,
|
||||||
|
context_store,
|
||||||
|
);
|
||||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||||
|
|
||||||
ThreadContextPicker { picker }
|
ThreadContextPicker { picker }
|
||||||
|
@ -44,13 +51,29 @@ impl Render for ThreadContextPicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ThreadContextEntry {
|
pub enum ThreadContextEntry {
|
||||||
pub id: ThreadId,
|
Thread {
|
||||||
pub summary: SharedString,
|
id: ThreadId,
|
||||||
|
title: SharedString,
|
||||||
|
},
|
||||||
|
Context {
|
||||||
|
path: Arc<Path>,
|
||||||
|
title: SharedString,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadContextEntry {
|
||||||
|
pub fn title(&self) -> &SharedString {
|
||||||
|
match self {
|
||||||
|
Self::Thread { title, .. } => title,
|
||||||
|
Self::Context { title, .. } => title,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ThreadContextPickerDelegate {
|
pub struct ThreadContextPickerDelegate {
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
text_thread_store: WeakEntity<TextThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
matches: Vec<ThreadContextEntry>,
|
matches: Vec<ThreadContextEntry>,
|
||||||
|
@ -60,6 +83,7 @@ pub struct ThreadContextPickerDelegate {
|
||||||
impl ThreadContextPickerDelegate {
|
impl ThreadContextPickerDelegate {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
text_thread_store: WeakEntity<TextThreadStore>,
|
||||||
context_picker: WeakEntity<ContextPicker>,
|
context_picker: WeakEntity<ContextPicker>,
|
||||||
context_store: WeakEntity<context_store::ContextStore>,
|
context_store: WeakEntity<context_store::ContextStore>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -67,6 +91,7 @@ impl ThreadContextPickerDelegate {
|
||||||
thread_store,
|
thread_store,
|
||||||
context_picker,
|
context_picker,
|
||||||
context_store,
|
context_store,
|
||||||
|
text_thread_store,
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
}
|
}
|
||||||
|
@ -103,11 +128,21 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
let Some(thread_store) = self.thread_store.upgrade() else {
|
let Some((thread_store, text_thread_context_store)) = self
|
||||||
|
.thread_store
|
||||||
|
.upgrade()
|
||||||
|
.zip(self.text_thread_store.upgrade())
|
||||||
|
else {
|
||||||
return Task::ready(());
|
return Task::ready(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let search_task = search_threads(query, Arc::new(AtomicBool::default()), thread_store, cx);
|
let search_task = search_threads(
|
||||||
|
query,
|
||||||
|
Arc::new(AtomicBool::default()),
|
||||||
|
thread_store,
|
||||||
|
text_thread_context_store,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let matches = search_task.await;
|
let matches = search_task.await;
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
@ -124,24 +159,48 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(thread_store) = self.thread_store.upgrade() else {
|
match entry {
|
||||||
return;
|
ThreadContextEntry::Thread { id, .. } => {
|
||||||
};
|
let Some(thread_store) = self.thread_store.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let open_thread_task =
|
||||||
|
thread_store.update(cx, |this, cx| this.open_thread(&id, cx));
|
||||||
|
|
||||||
let open_thread_task = thread_store.update(cx, |this, cx| this.open_thread(&entry.id, cx));
|
cx.spawn(async move |this, cx| {
|
||||||
|
let thread = open_thread_task.await?;
|
||||||
cx.spawn(async move |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
let thread = open_thread_task.await?;
|
this.delegate
|
||||||
this.update(cx, |this, cx| {
|
.context_store
|
||||||
this.delegate
|
.update(cx, |context_store, cx| {
|
||||||
.context_store
|
context_store.add_thread(thread, true, cx)
|
||||||
.update(cx, |context_store, cx| {
|
})
|
||||||
context_store.add_thread(thread, true, cx)
|
.ok();
|
||||||
})
|
})
|
||||||
.ok();
|
})
|
||||||
})
|
.detach_and_log_err(cx);
|
||||||
})
|
}
|
||||||
.detach_and_log_err(cx);
|
ThreadContextEntry::Context { path, .. } => {
|
||||||
|
let Some(text_thread_store) = self.text_thread_store.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let task = text_thread_store
|
||||||
|
.update(cx, |this, cx| this.open_local_context(path.clone(), cx));
|
||||||
|
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
|
let thread = task.await?;
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.delegate
|
||||||
|
.context_store
|
||||||
|
.update(cx, |context_store, cx| {
|
||||||
|
context_store.add_text_thread(thread, true, cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
@ -168,13 +227,20 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_thread_context_entry(
|
pub fn render_thread_context_entry(
|
||||||
thread: &ThreadContextEntry,
|
entry: &ThreadContextEntry,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Div {
|
) -> Div {
|
||||||
let added = context_store.upgrade().map_or(false, |ctx_store| {
|
let is_added = match entry {
|
||||||
ctx_store.read(cx).includes_thread(&thread.id)
|
ThreadContextEntry::Thread { id, .. } => context_store
|
||||||
});
|
.upgrade()
|
||||||
|
.map_or(false, |ctx_store| ctx_store.read(cx).includes_thread(&id)),
|
||||||
|
ThreadContextEntry::Context { path, .. } => {
|
||||||
|
context_store.upgrade().map_or(false, |ctx_store| {
|
||||||
|
ctx_store.read(cx).includes_text_thread(path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
|
@ -189,9 +255,9 @@ pub fn render_thread_context_entry(
|
||||||
.size(IconSize::XSmall)
|
.size(IconSize::XSmall)
|
||||||
.color(Color::Muted),
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
.child(Label::new(thread.summary.clone()).truncate()),
|
.child(Label::new(entry.title().clone()).truncate()),
|
||||||
)
|
)
|
||||||
.when(added, |el| {
|
.when(is_added, |el| {
|
||||||
el.child(
|
el.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
@ -211,28 +277,54 @@ pub struct ThreadMatch {
|
||||||
pub is_recent: bool,
|
pub is_recent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unordered_thread_entries(
|
||||||
|
thread_store: Entity<ThreadStore>,
|
||||||
|
text_thread_store: Entity<TextThreadStore>,
|
||||||
|
cx: &App,
|
||||||
|
) -> impl Iterator<Item = (DateTime<Utc>, ThreadContextEntry)> {
|
||||||
|
let threads = thread_store.read(cx).unordered_threads().map(|thread| {
|
||||||
|
(
|
||||||
|
thread.updated_at,
|
||||||
|
ThreadContextEntry::Thread {
|
||||||
|
id: thread.id.clone(),
|
||||||
|
title: thread.summary.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let text_threads = text_thread_store
|
||||||
|
.read(cx)
|
||||||
|
.unordered_contexts()
|
||||||
|
.map(|context| {
|
||||||
|
(
|
||||||
|
context.mtime.to_utc(),
|
||||||
|
ThreadContextEntry::Context {
|
||||||
|
path: context.path.clone(),
|
||||||
|
title: context.title.clone().into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
threads.chain(text_threads)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn search_threads(
|
pub(crate) fn search_threads(
|
||||||
query: String,
|
query: String,
|
||||||
cancellation_flag: Arc<AtomicBool>,
|
cancellation_flag: Arc<AtomicBool>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
|
text_thread_store: Entity<TextThreadStore>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Vec<ThreadMatch>> {
|
) -> Task<Vec<ThreadMatch>> {
|
||||||
let threads = thread_store
|
let mut threads =
|
||||||
.read(cx)
|
unordered_thread_entries(thread_store, text_thread_store, cx).collect::<Vec<_>>();
|
||||||
.reverse_chronological_threads()
|
threads.sort_unstable_by_key(|(updated_at, _)| std::cmp::Reverse(*updated_at));
|
||||||
.into_iter()
|
|
||||||
.map(|thread| ThreadContextEntry {
|
|
||||||
id: thread.id,
|
|
||||||
summary: thread.summary,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let executor = cx.background_executor().clone();
|
let executor = cx.background_executor().clone();
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
threads
|
threads
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|thread| ThreadMatch {
|
.map(|(_, thread)| ThreadMatch {
|
||||||
thread,
|
thread,
|
||||||
is_recent: false,
|
is_recent: false,
|
||||||
})
|
})
|
||||||
|
@ -241,7 +333,7 @@ pub(crate) fn search_threads(
|
||||||
let candidates = threads
|
let candidates = threads
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(id, thread)| StringMatchCandidate::new(id, &thread.summary))
|
.map(|(id, (_, thread))| StringMatchCandidate::new(id, &thread.title()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let matches = fuzzy::match_strings(
|
let matches = fuzzy::match_strings(
|
||||||
&candidates,
|
&candidates,
|
||||||
|
@ -256,7 +348,7 @@ pub(crate) fn search_threads(
|
||||||
matches
|
matches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mat| ThreadMatch {
|
.map(|mat| ThreadMatch {
|
||||||
thread: threads[mat.candidate_id].clone(),
|
thread: threads[mat.candidate_id].1.clone(),
|
||||||
is_recent: false,
|
is_recent: false,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
|
use assistant_context_editor::AssistantContext;
|
||||||
use collections::{HashSet, IndexSet};
|
use collections::{HashSet, IndexSet};
|
||||||
use futures::{self, FutureExt};
|
use futures::{self, FutureExt};
|
||||||
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
|
||||||
|
@ -18,7 +19,7 @@ use crate::ThreadStore;
|
||||||
use crate::context::{
|
use crate::context::{
|
||||||
AgentContextHandle, AgentContextKey, ContextId, DirectoryContextHandle, FetchedUrlContext,
|
AgentContextHandle, AgentContextKey, ContextId, DirectoryContextHandle, FetchedUrlContext,
|
||||||
FileContextHandle, ImageContext, RulesContextHandle, SelectionContextHandle,
|
FileContextHandle, ImageContext, RulesContextHandle, SelectionContextHandle,
|
||||||
SymbolContextHandle, ThreadContextHandle,
|
SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
|
||||||
};
|
};
|
||||||
use crate::context_strip::SuggestedContext;
|
use crate::context_strip::SuggestedContext;
|
||||||
use crate::thread::{MessageId, Thread, ThreadId};
|
use crate::thread::{MessageId, Thread, ThreadId};
|
||||||
|
@ -29,6 +30,7 @@ pub struct ContextStore {
|
||||||
next_context_id: ContextId,
|
next_context_id: ContextId,
|
||||||
context_set: IndexSet<AgentContextKey>,
|
context_set: IndexSet<AgentContextKey>,
|
||||||
context_thread_ids: HashSet<ThreadId>,
|
context_thread_ids: HashSet<ThreadId>,
|
||||||
|
context_text_thread_paths: HashSet<Arc<Path>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ContextStoreEvent {
|
pub enum ContextStoreEvent {
|
||||||
|
@ -48,6 +50,7 @@ impl ContextStore {
|
||||||
next_context_id: ContextId::zero(),
|
next_context_id: ContextId::zero(),
|
||||||
context_set: IndexSet::default(),
|
context_set: IndexSet::default(),
|
||||||
context_thread_ids: HashSet::default(),
|
context_thread_ids: HashSet::default(),
|
||||||
|
context_text_thread_paths: HashSet::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,6 +230,31 @@ impl ContextStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_text_thread(
|
||||||
|
&mut self,
|
||||||
|
context: Entity<AssistantContext>,
|
||||||
|
remove_if_exists: bool,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Option<AgentContextHandle> {
|
||||||
|
let context_id = self.next_context_id.post_inc();
|
||||||
|
let context = AgentContextHandle::TextThread(TextThreadContextHandle {
|
||||||
|
context,
|
||||||
|
context_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(existing) = self.context_set.get(AgentContextKey::ref_cast(&context)) {
|
||||||
|
if remove_if_exists {
|
||||||
|
self.remove_context(&context, cx);
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(existing.as_ref().clone())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.insert_context(context.clone(), cx);
|
||||||
|
Some(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_rules(
|
pub fn add_rules(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt_id: UserPromptId,
|
prompt_id: UserPromptId,
|
||||||
|
@ -364,6 +392,18 @@ impl ContextStore {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SuggestedContext::TextThread { context, name: _ } => {
|
||||||
|
if let Some(context) = context.upgrade() {
|
||||||
|
let context_id = self.next_context_id.post_inc();
|
||||||
|
self.insert_context(
|
||||||
|
AgentContextHandle::TextThread(TextThreadContextHandle {
|
||||||
|
context,
|
||||||
|
context_id,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,6 +420,10 @@ impl ContextStore {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AgentContextHandle::TextThread(text_thread_context) => {
|
||||||
|
self.context_text_thread_paths
|
||||||
|
.extend(text_thread_context.context.read(cx).path().cloned());
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
let inserted = self.context_set.insert(AgentContextKey(context));
|
let inserted = self.context_set.insert(AgentContextKey(context));
|
||||||
|
@ -399,6 +443,11 @@ impl ContextStore {
|
||||||
self.context_thread_ids
|
self.context_thread_ids
|
||||||
.remove(thread_context.thread.read(cx).id());
|
.remove(thread_context.thread.read(cx).id());
|
||||||
}
|
}
|
||||||
|
AgentContextHandle::TextThread(text_thread_context) => {
|
||||||
|
if let Some(path) = text_thread_context.context.read(cx).path() {
|
||||||
|
self.context_text_thread_paths.remove(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
cx.emit(ContextStoreEvent::ContextRemoved(key));
|
cx.emit(ContextStoreEvent::ContextRemoved(key));
|
||||||
|
@ -468,6 +517,10 @@ impl ContextStore {
|
||||||
self.context_thread_ids.contains(thread_id)
|
self.context_thread_ids.contains(thread_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn includes_text_thread(&self, path: &Arc<Path>) -> bool {
|
||||||
|
self.context_text_thread_paths.contains(path)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn includes_user_rules(&self, prompt_id: UserPromptId) -> bool {
|
pub fn includes_user_rules(&self, prompt_id: UserPromptId) -> bool {
|
||||||
self.context_set
|
self.context_set
|
||||||
.contains(&RulesContextHandle::lookup_key(prompt_id))
|
.contains(&RulesContextHandle::lookup_key(prompt_id))
|
||||||
|
@ -496,6 +549,7 @@ impl ContextStore {
|
||||||
| AgentContextHandle::Selection(_)
|
| AgentContextHandle::Selection(_)
|
||||||
| AgentContextHandle::FetchedUrl(_)
|
| AgentContextHandle::FetchedUrl(_)
|
||||||
| AgentContextHandle::Thread(_)
|
| AgentContextHandle::Thread(_)
|
||||||
|
| AgentContextHandle::TextThread(_)
|
||||||
| AgentContextHandle::Rules(_)
|
| AgentContextHandle::Rules(_)
|
||||||
| AgentContextHandle::Image(_) => None,
|
| AgentContextHandle::Image(_) => None,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use assistant_context_editor::AssistantContext;
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
|
@ -18,7 +19,7 @@ use crate::context::{AgentContextHandle, ContextKind};
|
||||||
use crate::context_picker::ContextPicker;
|
use crate::context_picker::ContextPicker;
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::thread::Thread;
|
use crate::thread::Thread;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
use crate::ui::{AddedContext, ContextPill};
|
use crate::ui::{AddedContext, ContextPill};
|
||||||
use crate::{
|
use crate::{
|
||||||
AcceptSuggestedContext, AssistantPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
AcceptSuggestedContext, AssistantPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
|
||||||
|
@ -43,6 +44,7 @@ impl ContextStrip {
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
suggest_context_kind: SuggestContextKind,
|
suggest_context_kind: SuggestContextKind,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
|
@ -52,6 +54,7 @@ impl ContextStrip {
|
||||||
ContextPicker::new(
|
ContextPicker::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store,
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -141,27 +144,42 @@ impl ContextStrip {
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspace = self.workspace.upgrade()?;
|
let workspace = self.workspace.upgrade()?;
|
||||||
let active_thread = workspace
|
let panel = workspace.read(cx).panel::<AssistantPanel>(cx)?.read(cx);
|
||||||
.read(cx)
|
|
||||||
.panel::<AssistantPanel>(cx)?
|
|
||||||
.read(cx)
|
|
||||||
.active_thread(cx);
|
|
||||||
let weak_active_thread = active_thread.downgrade();
|
|
||||||
|
|
||||||
let active_thread = active_thread.read(cx);
|
if let Some(active_thread) = panel.active_thread() {
|
||||||
|
let weak_active_thread = active_thread.downgrade();
|
||||||
|
|
||||||
if self
|
let active_thread = active_thread.read(cx);
|
||||||
.context_store
|
|
||||||
.read(cx)
|
if self
|
||||||
.includes_thread(active_thread.id())
|
.context_store
|
||||||
{
|
.read(cx)
|
||||||
return None;
|
.includes_thread(active_thread.id())
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(SuggestedContext::Thread {
|
||||||
|
name: active_thread.summary_or_default(),
|
||||||
|
thread: weak_active_thread,
|
||||||
|
})
|
||||||
|
} else if let Some(active_context_editor) = panel.active_context_editor() {
|
||||||
|
let context = active_context_editor.read(cx).context();
|
||||||
|
let weak_context = context.downgrade();
|
||||||
|
let context = context.read(cx);
|
||||||
|
let path = context.path()?;
|
||||||
|
|
||||||
|
if self.context_store.read(cx).includes_text_thread(path) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(SuggestedContext::TextThread {
|
||||||
|
name: context.summary_or_default(),
|
||||||
|
context: weak_context,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(SuggestedContext::Thread {
|
|
||||||
name: active_thread.summary_or_default(),
|
|
||||||
thread: weak_active_thread,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_context_picker_event(
|
fn handle_context_picker_event(
|
||||||
|
@ -538,6 +556,10 @@ pub enum SuggestedContext {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
thread: WeakEntity<Thread>,
|
thread: WeakEntity<Thread>,
|
||||||
},
|
},
|
||||||
|
TextThread {
|
||||||
|
name: SharedString,
|
||||||
|
context: WeakEntity<AssistantContext>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SuggestedContext {
|
impl SuggestedContext {
|
||||||
|
@ -545,6 +567,7 @@ impl SuggestedContext {
|
||||||
match self {
|
match self {
|
||||||
Self::File { name, .. } => name,
|
Self::File { name, .. } => name,
|
||||||
Self::Thread { name, .. } => name,
|
Self::Thread { name, .. } => name,
|
||||||
|
Self::TextThread { name, .. } => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,6 +575,7 @@ impl SuggestedContext {
|
||||||
match self {
|
match self {
|
||||||
Self::File { icon_path, .. } => icon_path.clone(),
|
Self::File { icon_path, .. } => icon_path.clone(),
|
||||||
Self::Thread { .. } => None,
|
Self::Thread { .. } => None,
|
||||||
|
Self::TextThread { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -559,6 +583,7 @@ impl SuggestedContext {
|
||||||
match self {
|
match self {
|
||||||
Self::File { .. } => ContextKind::File,
|
Self::File { .. } => ContextKind::File,
|
||||||
Self::Thread { .. } => ContextKind::Thread,
|
Self::Thread { .. } => ContextKind::Thread,
|
||||||
|
Self::TextThread { .. } => ContextKind::TextThread,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,10 @@ impl HistoryStore {
|
||||||
history_entries.push(HistoryEntry::Thread(thread));
|
history_entries.push(HistoryEntry::Thread(thread));
|
||||||
}
|
}
|
||||||
|
|
||||||
for context in self.context_store.update(cx, |this, _cx| this.contexts()) {
|
for context in self
|
||||||
|
.context_store
|
||||||
|
.update(cx, |this, _cx| this.reverse_chronological_contexts())
|
||||||
|
{
|
||||||
history_entries.push(HistoryEntry::Context(context));
|
history_entries.push(HistoryEntry::Context(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
|
use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
|
||||||
use crate::terminal_inline_assistant::TerminalInlineAssistant;
|
use crate::terminal_inline_assistant::TerminalInlineAssistant;
|
||||||
|
use crate::thread_store::TextThreadStore;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
|
@ -192,16 +193,20 @@ impl InlineAssistant {
|
||||||
if let Some(editor) = item.act_as::<Editor>(cx) {
|
if let Some(editor) = item.act_as::<Editor>(cx) {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
if is_assistant2_enabled {
|
if is_assistant2_enabled {
|
||||||
let thread_store = workspace
|
let panel = workspace.read(cx).panel::<AssistantPanel>(cx);
|
||||||
.read(cx)
|
let thread_store = panel
|
||||||
.panel::<AssistantPanel>(cx)
|
.as_ref()
|
||||||
.map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade());
|
.map(|assistant_panel| assistant_panel.read(cx).thread_store().downgrade());
|
||||||
|
let text_thread_store = panel.map(|assistant_panel| {
|
||||||
|
assistant_panel.read(cx).text_thread_store().downgrade()
|
||||||
|
});
|
||||||
|
|
||||||
editor.add_code_action_provider(
|
editor.add_code_action_provider(
|
||||||
Rc::new(AssistantCodeActionProvider {
|
Rc::new(AssistantCodeActionProvider {
|
||||||
editor: cx.entity().downgrade(),
|
editor: cx.entity().downgrade(),
|
||||||
workspace: workspace.downgrade(),
|
workspace: workspace.downgrade(),
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
}),
|
}),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -253,6 +258,8 @@ impl InlineAssistant {
|
||||||
.and_then(|assistant_panel| assistant_panel.prompt_store().as_ref().cloned());
|
.and_then(|assistant_panel| assistant_panel.prompt_store().as_ref().cloned());
|
||||||
let thread_store =
|
let thread_store =
|
||||||
assistant_panel.map(|assistant_panel| assistant_panel.thread_store().downgrade());
|
assistant_panel.map(|assistant_panel| assistant_panel.thread_store().downgrade());
|
||||||
|
let text_thread_store =
|
||||||
|
assistant_panel.map(|assistant_panel| assistant_panel.text_thread_store().downgrade());
|
||||||
|
|
||||||
let handle_assist =
|
let handle_assist =
|
||||||
|window: &mut Window, cx: &mut Context<Workspace>| match inline_assist_target {
|
|window: &mut Window, cx: &mut Context<Workspace>| match inline_assist_target {
|
||||||
|
@ -264,6 +271,7 @@ impl InlineAssistant {
|
||||||
workspace.project().downgrade(),
|
workspace.project().downgrade(),
|
||||||
prompt_store,
|
prompt_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -277,6 +285,7 @@ impl InlineAssistant {
|
||||||
workspace.project().downgrade(),
|
workspace.project().downgrade(),
|
||||||
prompt_store,
|
prompt_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -332,6 +341,7 @@ impl InlineAssistant {
|
||||||
project: WeakEntity<Project>,
|
project: WeakEntity<Project>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
|
@ -465,6 +475,7 @@ impl InlineAssistant {
|
||||||
context_store,
|
context_store,
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -537,6 +548,7 @@ impl InlineAssistant {
|
||||||
workspace: Entity<Workspace>,
|
workspace: Entity<Workspace>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> InlineAssistId {
|
) -> InlineAssistId {
|
||||||
|
@ -582,6 +594,7 @@ impl InlineAssistant {
|
||||||
context_store,
|
context_store,
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -1729,6 +1742,7 @@ struct AssistantCodeActionProvider {
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakEntity<Editor>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ASSISTANT_CODE_ACTION_PROVIDER_ID: &str = "assistant2";
|
const ASSISTANT_CODE_ACTION_PROVIDER_ID: &str = "assistant2";
|
||||||
|
@ -1803,6 +1817,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||||
let editor = self.editor.clone();
|
let editor = self.editor.clone();
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let thread_store = self.thread_store.clone();
|
let thread_store = self.thread_store.clone();
|
||||||
|
let text_thread_store = self.text_thread_store.clone();
|
||||||
let prompt_store = PromptStore::global(cx);
|
let prompt_store = PromptStore::global(cx);
|
||||||
window.spawn(cx, async move |cx| {
|
window.spawn(cx, async move |cx| {
|
||||||
let workspace = workspace.upgrade().context("workspace was released")?;
|
let workspace = workspace.upgrade().context("workspace was released")?;
|
||||||
|
@ -1855,6 +1870,7 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
||||||
workspace,
|
workspace,
|
||||||
prompt_store,
|
prompt_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::context_store::ContextStore;
|
||||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||||
use crate::message_editor::{extract_message_creases, insert_message_creases};
|
use crate::message_editor::{extract_message_creases, insert_message_creases};
|
||||||
use crate::terminal_codegen::TerminalCodegen;
|
use crate::terminal_codegen::TerminalCodegen;
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
||||||
use crate::{RemoveAllContext, ToggleContextPicker};
|
use crate::{RemoveAllContext, ToggleContextPicker};
|
||||||
use client::ErrorExt;
|
use client::ErrorExt;
|
||||||
|
@ -846,6 +846,7 @@ impl PromptEditor<BufferCodegen> {
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
cx: &mut Context<PromptEditor<BufferCodegen>>,
|
||||||
) -> PromptEditor<BufferCodegen> {
|
) -> PromptEditor<BufferCodegen> {
|
||||||
|
@ -889,6 +890,7 @@ impl PromptEditor<BufferCodegen> {
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
prompt_editor_entity,
|
prompt_editor_entity,
|
||||||
codegen_buffer.as_ref().map(Entity::downgrade),
|
codegen_buffer.as_ref().map(Entity::downgrade),
|
||||||
))));
|
))));
|
||||||
|
@ -902,6 +904,7 @@ impl PromptEditor<BufferCodegen> {
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::Thread,
|
SuggestContextKind::Thread,
|
||||||
window,
|
window,
|
||||||
|
@ -1023,6 +1026,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -1059,6 +1063,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
prompt_editor_entity,
|
prompt_editor_entity,
|
||||||
None,
|
None,
|
||||||
))));
|
))));
|
||||||
|
@ -1072,6 +1077,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::Thread,
|
SuggestContextKind::Thread,
|
||||||
window,
|
window,
|
||||||
|
|
|
@ -45,7 +45,7 @@ use crate::context_store::ContextStore;
|
||||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||||
use crate::profile_selector::ProfileSelector;
|
use crate::profile_selector::ProfileSelector;
|
||||||
use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
|
use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
use crate::{
|
use crate::{
|
||||||
ActiveThread, AgentDiffPane, Chat, ExpandMessageEditor, Follow, NewThread, OpenAgentDiff,
|
ActiveThread, AgentDiffPane, Chat, ExpandMessageEditor, Follow, NewThread, OpenAgentDiff,
|
||||||
RemoveAllContext, ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
RemoveAllContext, ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
||||||
|
@ -80,6 +80,7 @@ pub(crate) fn create_editor(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
context_store: WeakEntity<ContextStore>,
|
context_store: WeakEntity<ContextStore>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
text_thread_store: WeakEntity<TextThreadStore>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Entity<Editor> {
|
) -> Entity<Editor> {
|
||||||
|
@ -121,6 +122,7 @@ pub(crate) fn create_editor(
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
Some(thread_store),
|
Some(thread_store),
|
||||||
|
Some(text_thread_store),
|
||||||
editor_entity,
|
editor_entity,
|
||||||
None,
|
None,
|
||||||
))));
|
))));
|
||||||
|
@ -136,6 +138,7 @@ impl MessageEditor {
|
||||||
context_store: Entity<ContextStore>,
|
context_store: Entity<ContextStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
thread_store: WeakEntity<ThreadStore>,
|
||||||
|
text_thread_store: WeakEntity<TextThreadStore>,
|
||||||
thread: Entity<Thread>,
|
thread: Entity<Thread>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -147,6 +150,7 @@ impl MessageEditor {
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -156,6 +160,7 @@ impl MessageEditor {
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
Some(thread_store.clone()),
|
Some(thread_store.clone()),
|
||||||
|
Some(text_thread_store.clone()),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::File,
|
SuggestContextKind::File,
|
||||||
window,
|
window,
|
||||||
|
@ -1400,16 +1405,19 @@ impl AgentPreview for MessageEditor {
|
||||||
fn agent_preview(
|
fn agent_preview(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
active_thread: Entity<ActiveThread>,
|
active_thread: Entity<ActiveThread>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
if let Some(workspace) = workspace.upgrade() {
|
if let Some(workspace) = workspace.upgrade() {
|
||||||
let fs = workspace.read(cx).app_state().fs.clone();
|
let fs = workspace.read(cx).app_state().fs.clone();
|
||||||
let user_store = workspace.read(cx).app_state().user_store.clone();
|
let user_store = workspace.read(cx).app_state().user_store.clone();
|
||||||
let weak_project = workspace.read(cx).project().clone().downgrade();
|
let project = workspace.read(cx).project().clone();
|
||||||
|
let weak_project = project.downgrade();
|
||||||
let context_store = cx.new(|_cx| ContextStore::new(weak_project, None));
|
let context_store = cx.new(|_cx| ContextStore::new(weak_project, None));
|
||||||
let thread = active_thread.read(cx).thread().clone();
|
let active_thread = active_thread.read(cx);
|
||||||
|
let thread = active_thread.thread().clone();
|
||||||
|
let thread_store = active_thread.thread_store().clone();
|
||||||
|
let text_thread_store = active_thread.text_thread_store().clone();
|
||||||
|
|
||||||
let default_message_editor = cx.new(|cx| {
|
let default_message_editor = cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
|
@ -1418,7 +1426,8 @@ impl AgentPreview for MessageEditor {
|
||||||
user_store,
|
user_store,
|
||||||
context_store,
|
context_store,
|
||||||
None,
|
None,
|
||||||
thread_store,
|
thread_store.downgrade(),
|
||||||
|
text_thread_store.downgrade(),
|
||||||
thread,
|
thread,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::inline_prompt_editor::{
|
||||||
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
|
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
|
||||||
};
|
};
|
||||||
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
|
use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::{TextThreadStore, ThreadStore};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{HashMap, VecDeque};
|
use collections::{HashMap, VecDeque};
|
||||||
|
@ -71,6 +71,7 @@ impl TerminalInlineAssistant {
|
||||||
project: WeakEntity<Project>,
|
project: WeakEntity<Project>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<WeakEntity<TextThreadStore>>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
|
@ -91,6 +92,7 @@ impl TerminalInlineAssistant {
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
|
text_thread_store.clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -58,6 +58,8 @@ impl SharedProjectContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type TextThreadStore = assistant_context_editor::ContextStore;
|
||||||
|
|
||||||
pub struct ThreadStore {
|
pub struct ThreadStore {
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
tools: Entity<ToolWorkingSet>,
|
tools: Entity<ToolWorkingSet>,
|
||||||
|
@ -361,6 +363,10 @@ impl ThreadStore {
|
||||||
self.threads.len()
|
self.threads.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unordered_threads(&self) -> impl Iterator<Item = &SerializedThreadMetadata> {
|
||||||
|
self.threads.iter()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reverse_chronological_threads(&self) -> Vec<SerializedThreadMetadata> {
|
pub fn reverse_chronological_threads(&self) -> Vec<SerializedThreadMetadata> {
|
||||||
let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();
|
let mut threads = self.threads.iter().cloned().collect::<Vec<_>>();
|
||||||
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));
|
threads.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.updated_at));
|
||||||
|
|
|
@ -16,7 +16,8 @@ use crate::context::{
|
||||||
AgentContext, AgentContextHandle, ContextId, ContextKind, DirectoryContext,
|
AgentContext, AgentContextHandle, ContextId, ContextKind, DirectoryContext,
|
||||||
DirectoryContextHandle, FetchedUrlContext, FileContext, FileContextHandle, ImageContext,
|
DirectoryContextHandle, FetchedUrlContext, FileContext, FileContextHandle, ImageContext,
|
||||||
ImageStatus, RulesContext, RulesContextHandle, SelectionContext, SelectionContextHandle,
|
ImageStatus, RulesContext, RulesContextHandle, SelectionContext, SelectionContextHandle,
|
||||||
SymbolContext, SymbolContextHandle, ThreadContext, ThreadContextHandle,
|
SymbolContext, SymbolContextHandle, TextThreadContext, TextThreadContextHandle, ThreadContext,
|
||||||
|
ThreadContextHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
|
@ -301,6 +302,7 @@ impl AddedContext {
|
||||||
AgentContextHandle::Selection(handle) => Self::pending_selection(handle, cx),
|
AgentContextHandle::Selection(handle) => Self::pending_selection(handle, cx),
|
||||||
AgentContextHandle::FetchedUrl(handle) => Some(Self::fetched_url(handle)),
|
AgentContextHandle::FetchedUrl(handle) => Some(Self::fetched_url(handle)),
|
||||||
AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)),
|
AgentContextHandle::Thread(handle) => Some(Self::pending_thread(handle, cx)),
|
||||||
|
AgentContextHandle::TextThread(handle) => Some(Self::pending_text_thread(handle, cx)),
|
||||||
AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx),
|
AgentContextHandle::Rules(handle) => Self::pending_rules(handle, prompt_store, cx),
|
||||||
AgentContextHandle::Image(handle) => Some(Self::image(handle)),
|
AgentContextHandle::Image(handle) => Some(Self::image(handle)),
|
||||||
}
|
}
|
||||||
|
@ -314,6 +316,7 @@ impl AddedContext {
|
||||||
AgentContext::Selection(context) => Self::attached_selection(context, cx),
|
AgentContext::Selection(context) => Self::attached_selection(context, cx),
|
||||||
AgentContext::FetchedUrl(context) => Self::fetched_url(context.clone()),
|
AgentContext::FetchedUrl(context) => Self::fetched_url(context.clone()),
|
||||||
AgentContext::Thread(context) => Self::attached_thread(context),
|
AgentContext::Thread(context) => Self::attached_thread(context),
|
||||||
|
AgentContext::TextThread(context) => Self::attached_text_thread(context),
|
||||||
AgentContext::Rules(context) => Self::attached_rules(context),
|
AgentContext::Rules(context) => Self::attached_rules(context),
|
||||||
AgentContext::Image(context) => Self::image(context.clone()),
|
AgentContext::Image(context) => Self::image(context.clone()),
|
||||||
}
|
}
|
||||||
|
@ -520,6 +523,43 @@ impl AddedContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pending_text_thread(handle: TextThreadContextHandle, cx: &App) -> AddedContext {
|
||||||
|
AddedContext {
|
||||||
|
kind: ContextKind::TextThread,
|
||||||
|
name: handle.title(cx),
|
||||||
|
parent: None,
|
||||||
|
tooltip: None,
|
||||||
|
icon_path: None,
|
||||||
|
status: ContextStatus::Ready,
|
||||||
|
render_hover: {
|
||||||
|
let context = handle.context.clone();
|
||||||
|
Some(Rc::new(move |_, cx| {
|
||||||
|
let text = context.read(cx).to_xml(cx);
|
||||||
|
ContextPillHover::new_text(text.into(), cx).into()
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
handle: AgentContextHandle::TextThread(handle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attached_text_thread(context: &TextThreadContext) -> AddedContext {
|
||||||
|
AddedContext {
|
||||||
|
kind: ContextKind::TextThread,
|
||||||
|
name: context.title.clone(),
|
||||||
|
parent: None,
|
||||||
|
tooltip: None,
|
||||||
|
icon_path: None,
|
||||||
|
status: ContextStatus::Ready,
|
||||||
|
render_hover: {
|
||||||
|
let text = context.text.clone();
|
||||||
|
Some(Rc::new(move |_, cx| {
|
||||||
|
ContextPillHover::new_text(text.clone(), cx).into()
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
handle: AgentContextHandle::TextThread(context.handle.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn pending_rules(
|
fn pending_rules(
|
||||||
handle: RulesContextHandle,
|
handle: RulesContextHandle,
|
||||||
prompt_store: Option<&Entity<PromptStore>>,
|
prompt_store: Option<&Entity<PromptStore>>,
|
||||||
|
|
|
@ -6,16 +6,11 @@ use std::sync::OnceLock;
|
||||||
use ui::{AnyElement, Component, ComponentScope, Window};
|
use ui::{AnyElement, Component, ComponentScope, Window};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::{ActiveThread, ThreadStore};
|
use crate::ActiveThread;
|
||||||
|
|
||||||
/// Function type for creating agent component previews
|
/// Function type for creating agent component previews
|
||||||
pub type PreviewFn = fn(
|
pub type PreviewFn =
|
||||||
WeakEntity<Workspace>,
|
fn(WeakEntity<Workspace>, Entity<ActiveThread>, &mut Window, &mut App) -> Option<AnyElement>;
|
||||||
Entity<ActiveThread>,
|
|
||||||
WeakEntity<ThreadStore>,
|
|
||||||
&mut Window,
|
|
||||||
&mut App,
|
|
||||||
) -> Option<AnyElement>;
|
|
||||||
|
|
||||||
/// Distributed slice for preview registration functions
|
/// Distributed slice for preview registration functions
|
||||||
#[distributed_slice]
|
#[distributed_slice]
|
||||||
|
@ -32,7 +27,6 @@ pub trait AgentPreview: Component + Sized {
|
||||||
fn agent_preview(
|
fn agent_preview(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
active_thread: Entity<ActiveThread>,
|
active_thread: Entity<ActiveThread>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Option<AnyElement>;
|
) -> Option<AnyElement>;
|
||||||
|
@ -75,14 +69,13 @@ pub fn get_agent_preview(
|
||||||
id: &ComponentId,
|
id: &ComponentId,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
active_thread: Entity<ActiveThread>,
|
active_thread: Entity<ActiveThread>,
|
||||||
thread_store: WeakEntity<ThreadStore>,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let registry = get_or_init_registry();
|
let registry = get_or_init_registry();
|
||||||
registry
|
registry
|
||||||
.get(id)
|
.get(id)
|
||||||
.and_then(|preview_fn| preview_fn(workspace, active_thread, thread_store, window, cx))
|
.and_then(|preview_fn| preview_fn(workspace, active_thread, window, cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all registered agent previews.
|
/// Get all registered agent previews.
|
||||||
|
|
|
@ -32,7 +32,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{Ordering, max},
|
cmp::{Ordering, max},
|
||||||
fmt::Debug,
|
fmt::{Debug, Write as _},
|
||||||
iter, mem,
|
iter, mem,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::Path,
|
path::Path,
|
||||||
|
@ -2539,6 +2539,26 @@ impl AssistantContext {
|
||||||
Some(user_message)
|
Some(user_message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_xml(&self, cx: &App) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
let buffer = self.buffer.read(cx);
|
||||||
|
for message in self.messages(cx) {
|
||||||
|
if message.status != MessageStatus::Done {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(&mut output, "<{}>", message.role).unwrap();
|
||||||
|
for chunk in buffer.text_for_range(message.offset_range) {
|
||||||
|
output.push_str(chunk);
|
||||||
|
}
|
||||||
|
if !output.ends_with('\n') {
|
||||||
|
output.push('\n');
|
||||||
|
}
|
||||||
|
writeln!(&mut output, "</{}>", message.role).unwrap();
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_completion_request(
|
pub fn to_completion_request(
|
||||||
&self,
|
&self,
|
||||||
request_type: RequestType,
|
request_type: RequestType,
|
||||||
|
|
|
@ -339,7 +339,11 @@ impl ContextStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contexts(&self) -> Vec<SavedContextMetadata> {
|
pub fn unordered_contexts(&self) -> impl Iterator<Item = &SavedContextMetadata> {
|
||||||
|
self.contexts_metadata.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reverse_chronological_contexts(&self) -> Vec<SavedContextMetadata> {
|
||||||
let mut contexts = self.contexts_metadata.iter().cloned().collect::<Vec<_>>();
|
let mut contexts = self.contexts_metadata.iter().cloned().collect::<Vec<_>>();
|
||||||
contexts.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.mtime));
|
contexts.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.mtime));
|
||||||
contexts
|
contexts
|
||||||
|
|
|
@ -21,6 +21,7 @@ client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
component.workspace = true
|
component.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
languages.workspace = true
|
languages.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
@ -30,6 +31,7 @@ prompt_store.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
ui_input.workspace = true
|
ui_input.workspace = true
|
||||||
|
util.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
assistant_tool.workspace = true
|
assistant_tool.workspace = true
|
||||||
|
|
|
@ -8,7 +8,7 @@ mod preview_support;
|
||||||
use std::iter::Iterator;
|
use std::iter::Iterator;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use agent::{ActiveThread, ThreadStore};
|
use agent::{ActiveThread, TextThreadStore, ThreadStore};
|
||||||
use client::UserStore;
|
use client::UserStore;
|
||||||
use component::{ComponentId, ComponentMetadata, components};
|
use component::{ComponentId, ComponentMetadata, components};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -21,11 +21,13 @@ use gpui::{ListState, ScrollHandle, ScrollStrategy, UniformListScrollHandle};
|
||||||
use languages::LanguageRegistry;
|
use languages::LanguageRegistry;
|
||||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||||
use persistence::COMPONENT_PREVIEW_DB;
|
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 project::Project;
|
||||||
use ui::{Divider, HighlightedLabel, ListItem, ListSubHeader, prelude::*};
|
use ui::{Divider, HighlightedLabel, ListItem, ListSubHeader, prelude::*};
|
||||||
|
|
||||||
use ui_input::SingleLineInput;
|
use ui_input::SingleLineInput;
|
||||||
|
use util::ResultExt as _;
|
||||||
use workspace::{AppState, ItemId, SerializableItem, delete_unloaded_items};
|
use workspace::{AppState, ItemId, SerializableItem, delete_unloaded_items};
|
||||||
use workspace::{Item, Workspace, WorkspaceId, item::ItemEvent};
|
use workspace::{Item, Workspace, WorkspaceId, item::ItemEvent};
|
||||||
|
|
||||||
|
@ -120,6 +122,7 @@ struct ComponentPreview {
|
||||||
|
|
||||||
// preview support
|
// preview support
|
||||||
thread_store: Option<Entity<ThreadStore>>,
|
thread_store: Option<Entity<ThreadStore>>,
|
||||||
|
text_thread_store: Option<Entity<TextThreadStore>>,
|
||||||
active_thread: Option<Entity<ActiveThread>>,
|
active_thread: Option<Entity<ActiveThread>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,23 +140,29 @@ impl ComponentPreview {
|
||||||
let workspace_clone = workspace.clone();
|
let workspace_clone = workspace.clone();
|
||||||
let project_clone = project.clone();
|
let project_clone = project.clone();
|
||||||
|
|
||||||
let entity = cx.weak_entity();
|
cx.spawn_in(window, async move |entity, cx| {
|
||||||
window
|
let thread_store_future =
|
||||||
.spawn(cx, async move |cx| {
|
load_preview_thread_store(workspace_clone.clone(), project_clone.clone(), cx);
|
||||||
let thread_store_task =
|
let text_thread_store_future =
|
||||||
load_preview_thread_store(workspace_clone.clone(), project_clone.clone(), cx)
|
load_preview_text_thread_store(workspace_clone.clone(), project_clone.clone(), cx);
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Ok(thread_store) = thread_store_task.await {
|
let (thread_store_result, text_thread_store_result) =
|
||||||
entity
|
futures::join!(thread_store_future, text_thread_store_future);
|
||||||
.update_in(cx, |this, window, cx| {
|
|
||||||
this.thread_store = Some(thread_store.clone());
|
if let (Some(thread_store), Some(text_thread_store)) = (
|
||||||
this.create_active_thread(window, cx);
|
thread_store_result.log_err(),
|
||||||
})
|
text_thread_store_result.log_err(),
|
||||||
.ok();
|
) {
|
||||||
}
|
entity
|
||||||
})
|
.update_in(cx, |this, window, cx| {
|
||||||
.detach();
|
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 sorted_components = components().all_sorted();
|
||||||
let selected_index = selected_index.into().unwrap_or(0);
|
let selected_index = selected_index.into().unwrap_or(0);
|
||||||
|
@ -195,6 +204,7 @@ impl ComponentPreview {
|
||||||
filter_editor,
|
filter_editor,
|
||||||
filter_text: String::new(),
|
filter_text: String::new(),
|
||||||
thread_store: None,
|
thread_store: None,
|
||||||
|
text_thread_store: None,
|
||||||
active_thread: None,
|
active_thread: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -220,12 +230,17 @@ impl ComponentPreview {
|
||||||
let weak_handle = self.workspace.clone();
|
let weak_handle = self.workspace.clone();
|
||||||
if let Some(workspace) = workspace.upgrade() {
|
if let Some(workspace) = workspace.upgrade() {
|
||||||
let project = workspace.read(cx).project().clone();
|
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(
|
let active_thread = static_active_thread(
|
||||||
weak_handle,
|
weak_handle,
|
||||||
project,
|
project,
|
||||||
language_registry,
|
language_registry,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -625,15 +640,11 @@ impl ComponentPreview {
|
||||||
|
|
||||||
// Check if the component's scope is Agent
|
// Check if the component's scope is Agent
|
||||||
if scope == ComponentScope::Agent {
|
if scope == ComponentScope::Agent {
|
||||||
if let (Some(thread_store), Some(active_thread)) = (
|
if let Some(active_thread) = self.active_thread.clone() {
|
||||||
self.thread_store.as_ref().map(|ts| ts.downgrade()),
|
|
||||||
self.active_thread.clone(),
|
|
||||||
) {
|
|
||||||
if let Some(element) = agent::get_agent_preview(
|
if let Some(element) = agent::get_agent_preview(
|
||||||
&component.id(),
|
&component.id(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
active_thread,
|
active_thread,
|
||||||
thread_store,
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
) {
|
) {
|
||||||
|
@ -688,7 +699,6 @@ impl ComponentPreview {
|
||||||
.child(ComponentPreviewPage::new(
|
.child(ComponentPreviewPage::new(
|
||||||
component.clone(),
|
component.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
self.thread_store.as_ref().map(|ts| ts.downgrade()),
|
|
||||||
self.active_thread.clone(),
|
self.active_thread.clone(),
|
||||||
))
|
))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
|
@ -1037,7 +1047,6 @@ pub struct ComponentPreviewPage {
|
||||||
// languages: Arc<LanguageRegistry>,
|
// languages: Arc<LanguageRegistry>,
|
||||||
component: ComponentMetadata,
|
component: ComponentMetadata,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
|
||||||
active_thread: Option<Entity<ActiveThread>>,
|
active_thread: Option<Entity<ActiveThread>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1045,7 +1054,6 @@ impl ComponentPreviewPage {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
component: ComponentMetadata,
|
component: ComponentMetadata,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
|
||||||
active_thread: Option<Entity<ActiveThread>>,
|
active_thread: Option<Entity<ActiveThread>>,
|
||||||
// languages: Arc<LanguageRegistry>
|
// languages: Arc<LanguageRegistry>
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -1053,7 +1061,6 @@ impl ComponentPreviewPage {
|
||||||
// languages,
|
// languages,
|
||||||
component,
|
component,
|
||||||
workspace,
|
workspace,
|
||||||
thread_store,
|
|
||||||
active_thread,
|
active_thread,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1086,14 +1093,11 @@ impl ComponentPreviewPage {
|
||||||
|
|
||||||
fn render_preview(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render_preview(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
// Try to get agent preview first if we have an active thread
|
// Try to get agent preview first if we have an active thread
|
||||||
let maybe_agent_preview = if let (Some(thread_store), Some(active_thread)) =
|
let maybe_agent_preview = if let Some(active_thread) = self.active_thread.as_ref() {
|
||||||
(self.thread_store.as_ref(), self.active_thread.as_ref())
|
|
||||||
{
|
|
||||||
agent::get_agent_preview(
|
agent::get_agent_preview(
|
||||||
&self.component.id(),
|
&self.component.id(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
active_thread.clone(),
|
active_thread.clone(),
|
||||||
thread_store.clone(),
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,31 +2,47 @@ use languages::LanguageRegistry;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::sync::Arc;
|
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 assistant_tool::ToolWorkingSet;
|
||||||
use gpui::{AppContext, AsyncApp, Entity, Task, WeakEntity};
|
use gpui::{AppContext, AsyncApp, Entity, Task, WeakEntity};
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use ui::{App, Window};
|
use ui::{App, Window};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub async fn load_preview_thread_store(
|
pub fn load_preview_thread_store(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Task<anyhow::Result<Entity<ThreadStore>>> {
|
) -> Task<Result<Entity<ThreadStore>>> {
|
||||||
cx.spawn(async move |cx| {
|
workspace
|
||||||
workspace
|
.update(cx, |_, cx| {
|
||||||
.update(cx, |_, cx| {
|
ThreadStore::load(
|
||||||
ThreadStore::load(
|
project.clone(),
|
||||||
project.clone(),
|
cx.new(|_| ToolWorkingSet::default()),
|
||||||
cx.new(|_| ToolWorkingSet::default()),
|
None,
|
||||||
None,
|
Arc::new(PromptBuilder::new(None).unwrap()),
|
||||||
Arc::new(PromptBuilder::new(None).unwrap()),
|
cx,
|
||||||
cx,
|
)
|
||||||
)
|
})
|
||||||
})?
|
.unwrap_or(Task::ready(Err(anyhow!("workspace dropped"))))
|
||||||
.await
|
}
|
||||||
})
|
|
||||||
|
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(
|
pub fn static_active_thread(
|
||||||
|
@ -34,6 +50,7 @@ pub fn static_active_thread(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
thread_store: Entity<ThreadStore>,
|
thread_store: Entity<ThreadStore>,
|
||||||
|
text_thread_store: Entity<TextThreadStore>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Entity<ActiveThread> {
|
) -> Entity<ActiveThread> {
|
||||||
|
@ -59,6 +76,7 @@ pub fn static_active_thread(
|
||||||
ActiveThread::new(
|
ActiveThread::new(
|
||||||
thread,
|
thread,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
text_thread_store,
|
||||||
context_store,
|
context_store,
|
||||||
language_registry,
|
language_registry,
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue