assistant: Allow guests to create new contexts on the host (#15439)
This PR extends collaboration in the Assistant to allow guests to create new contexts on the host when collaborating. Release Notes: - N/A
This commit is contained in:
parent
2b0c60043d
commit
aa1633ba40
5 changed files with 225 additions and 24 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::ContextStoreEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
assistant_settings::{AssistantDockPosition, AssistantSettings},
|
assistant_settings::{AssistantDockPosition, AssistantSettings},
|
||||||
humanize_token_count,
|
humanize_token_count,
|
||||||
|
@ -389,6 +390,7 @@ impl AssistantPanel {
|
||||||
cx.subscribe(&pane, Self::handle_pane_event),
|
cx.subscribe(&pane, Self::handle_pane_event),
|
||||||
cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
|
cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
|
||||||
cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
|
cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
|
||||||
|
cx.subscribe(&context_store, Self::handle_context_store_event),
|
||||||
cx.observe(
|
cx.observe(
|
||||||
&LanguageModelCompletionProvider::global(cx),
|
&LanguageModelCompletionProvider::global(cx),
|
||||||
|this, _, cx| {
|
|this, _, cx| {
|
||||||
|
@ -507,6 +509,46 @@ impl AssistantPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_context_store_event(
|
||||||
|
&mut self,
|
||||||
|
_context_store: Model<ContextStore>,
|
||||||
|
event: &ContextStoreEvent,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
let ContextStoreEvent::ContextCreated(context_id) = event;
|
||||||
|
let Some(context) = self
|
||||||
|
.context_store
|
||||||
|
.read(cx)
|
||||||
|
.loaded_context_for_id(&context_id, cx)
|
||||||
|
else {
|
||||||
|
log::error!("no context found with ID: {}", context_id.to_proto());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let lsp_adapter_delegate = workspace.update(cx, |workspace, cx| {
|
||||||
|
make_lsp_adapter_delegate(workspace.project(), cx).log_err()
|
||||||
|
});
|
||||||
|
|
||||||
|
let assistant_panel = cx.view().downgrade();
|
||||||
|
let editor = cx.new_view(|cx| {
|
||||||
|
let mut editor = ContextEditor::for_context(
|
||||||
|
context,
|
||||||
|
self.fs.clone(),
|
||||||
|
workspace.clone(),
|
||||||
|
self.project.clone(),
|
||||||
|
lsp_adapter_delegate,
|
||||||
|
assistant_panel,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.insert_default_prompt(cx);
|
||||||
|
editor
|
||||||
|
});
|
||||||
|
|
||||||
|
self.show_context(editor.clone(), cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn completion_provider_changed(&mut self, cx: &mut ViewContext<Self>) {
|
fn completion_provider_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(editor) = self.active_context_editor(cx) {
|
if let Some(editor) = self.active_context_editor(cx) {
|
||||||
editor.update(cx, |active_context, cx| {
|
editor.update(cx, |active_context, cx| {
|
||||||
|
@ -681,29 +723,75 @@ impl AssistantPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
||||||
let context = self.context_store.update(cx, |store, cx| store.create(cx));
|
if self.project.read(cx).is_remote() {
|
||||||
let workspace = self.workspace.upgrade()?;
|
let task = self
|
||||||
let lsp_adapter_delegate = workspace.update(cx, |workspace, cx| {
|
.context_store
|
||||||
make_lsp_adapter_delegate(workspace.project(), cx).log_err()
|
.update(cx, |store, cx| store.create_remote_context(cx));
|
||||||
});
|
|
||||||
|
|
||||||
let assistant_panel = cx.view().downgrade();
|
cx.spawn(|this, mut cx| async move {
|
||||||
let editor = cx.new_view(|cx| {
|
let context = task.await?;
|
||||||
let mut editor = ContextEditor::for_context(
|
|
||||||
context,
|
|
||||||
self.fs.clone(),
|
|
||||||
workspace.clone(),
|
|
||||||
self.project.clone(),
|
|
||||||
lsp_adapter_delegate,
|
|
||||||
assistant_panel,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
editor.insert_default_prompt(cx);
|
|
||||||
editor
|
|
||||||
});
|
|
||||||
|
|
||||||
self.show_context(editor.clone(), cx);
|
this.update(&mut cx, |this, cx| {
|
||||||
Some(editor)
|
let Some(workspace) = this.workspace.upgrade() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let lsp_adapter_delegate = workspace.update(cx, |workspace, cx| {
|
||||||
|
make_lsp_adapter_delegate(workspace.project(), cx).log_err()
|
||||||
|
});
|
||||||
|
|
||||||
|
let fs = this.fs.clone();
|
||||||
|
let project = this.project.clone();
|
||||||
|
let weak_assistant_panel = cx.view().downgrade();
|
||||||
|
|
||||||
|
let editor = cx.new_view(|cx| {
|
||||||
|
let mut editor = ContextEditor::for_context(
|
||||||
|
context,
|
||||||
|
fs,
|
||||||
|
workspace.clone(),
|
||||||
|
project,
|
||||||
|
lsp_adapter_delegate,
|
||||||
|
weak_assistant_panel,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.insert_default_prompt(cx);
|
||||||
|
editor
|
||||||
|
});
|
||||||
|
|
||||||
|
this.show_context(editor, cx);
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})??;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let context = self.context_store.update(cx, |store, cx| store.create(cx));
|
||||||
|
let workspace = self.workspace.upgrade()?;
|
||||||
|
let lsp_adapter_delegate = workspace.update(cx, |workspace, cx| {
|
||||||
|
make_lsp_adapter_delegate(workspace.project(), cx).log_err()
|
||||||
|
});
|
||||||
|
|
||||||
|
let assistant_panel = cx.view().downgrade();
|
||||||
|
let editor = cx.new_view(|cx| {
|
||||||
|
let mut editor = ContextEditor::for_context(
|
||||||
|
context,
|
||||||
|
self.fs.clone(),
|
||||||
|
workspace.clone(),
|
||||||
|
self.project.clone(),
|
||||||
|
lsp_adapter_delegate,
|
||||||
|
assistant_panel,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
editor.insert_default_prompt(cx);
|
||||||
|
editor
|
||||||
|
});
|
||||||
|
|
||||||
|
self.show_context(editor.clone(), cx);
|
||||||
|
Some(editor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
|
fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
|
||||||
|
|
|
@ -8,7 +8,9 @@ use clock::ReplicaId;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, Task, WeakModel};
|
use gpui::{
|
||||||
|
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||||
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use paths::contexts_dir;
|
use paths::contexts_dir;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
@ -26,6 +28,7 @@ use util::{ResultExt, TryFutureExt};
|
||||||
pub fn init(client: &Arc<Client>) {
|
pub fn init(client: &Arc<Client>) {
|
||||||
client.add_model_message_handler(ContextStore::handle_advertise_contexts);
|
client.add_model_message_handler(ContextStore::handle_advertise_contexts);
|
||||||
client.add_model_request_handler(ContextStore::handle_open_context);
|
client.add_model_request_handler(ContextStore::handle_open_context);
|
||||||
|
client.add_model_request_handler(ContextStore::handle_create_context);
|
||||||
client.add_model_message_handler(ContextStore::handle_update_context);
|
client.add_model_message_handler(ContextStore::handle_update_context);
|
||||||
client.add_model_request_handler(ContextStore::handle_synchronize_contexts);
|
client.add_model_request_handler(ContextStore::handle_synchronize_contexts);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +54,12 @@ pub struct ContextStore {
|
||||||
_project_subscriptions: Vec<gpui::Subscription>,
|
_project_subscriptions: Vec<gpui::Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ContextStoreEvent {
|
||||||
|
ContextCreated(ContextId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<ContextStoreEvent> for ContextStore {}
|
||||||
|
|
||||||
enum ContextHandle {
|
enum ContextHandle {
|
||||||
Weak(WeakModel<Context>),
|
Weak(WeakModel<Context>),
|
||||||
Strong(Model<Context>),
|
Strong(Model<Context>),
|
||||||
|
@ -169,6 +178,34 @@ impl ContextStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_create_context(
|
||||||
|
this: Model<Self>,
|
||||||
|
_: TypedEnvelope<proto::CreateContext>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<proto::CreateContextResponse> {
|
||||||
|
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
||||||
|
if this.project.read(cx).is_remote() {
|
||||||
|
return Err(anyhow!("can only create contexts as the host"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = this.create(cx);
|
||||||
|
let context_id = context.read(cx).id().clone();
|
||||||
|
cx.emit(ContextStoreEvent::ContextCreated(context_id.clone()));
|
||||||
|
|
||||||
|
anyhow::Ok((
|
||||||
|
context_id,
|
||||||
|
context
|
||||||
|
.read(cx)
|
||||||
|
.serialize_ops(&ContextVersion::default(), cx),
|
||||||
|
))
|
||||||
|
})??;
|
||||||
|
let operations = operations.await;
|
||||||
|
Ok(proto::CreateContextResponse {
|
||||||
|
context_id: context_id.to_proto(),
|
||||||
|
context: Some(proto::Context { operations }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_update_context(
|
async fn handle_update_context(
|
||||||
this: Model<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::UpdateContext>,
|
envelope: TypedEnvelope<proto::UpdateContext>,
|
||||||
|
@ -299,6 +336,60 @@ impl ContextStore {
|
||||||
context
|
context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_remote_context(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<Result<Model<Context>>> {
|
||||||
|
let project = self.project.read(cx);
|
||||||
|
let Some(project_id) = project.remote_id() else {
|
||||||
|
return Task::ready(Err(anyhow!("project was not remote")));
|
||||||
|
};
|
||||||
|
if project.is_local() {
|
||||||
|
return Task::ready(Err(anyhow!("cannot create remote contexts as the host")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let replica_id = project.replica_id();
|
||||||
|
let capability = project.capability();
|
||||||
|
let language_registry = self.languages.clone();
|
||||||
|
let telemetry = self.telemetry.clone();
|
||||||
|
let request = self.client.request(proto::CreateContext { project_id });
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let response = request.await?;
|
||||||
|
let context_id = ContextId::from_proto(response.context_id);
|
||||||
|
let context_proto = response.context.context("invalid context")?;
|
||||||
|
let context = cx.new_model(|cx| {
|
||||||
|
Context::new(
|
||||||
|
context_id.clone(),
|
||||||
|
replica_id,
|
||||||
|
capability,
|
||||||
|
language_registry,
|
||||||
|
Some(telemetry),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let operations = cx
|
||||||
|
.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
context_proto
|
||||||
|
.operations
|
||||||
|
.into_iter()
|
||||||
|
.map(|op| ContextOperation::from_proto(op))
|
||||||
|
.collect::<Result<Vec<_>>>()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
context.update(&mut cx, |context, cx| context.apply_ops(operations, cx))??;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
if let Some(existing_context) = this.loaded_context_for_id(&context_id, cx) {
|
||||||
|
existing_context
|
||||||
|
} else {
|
||||||
|
this.register_context(&context, cx);
|
||||||
|
this.synchronize_contexts(cx);
|
||||||
|
context
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_local_context(
|
pub fn open_local_context(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -346,7 +437,11 @@ impl ContextStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option<Model<Context>> {
|
pub(super) fn loaded_context_for_id(
|
||||||
|
&self,
|
||||||
|
id: &ContextId,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Option<Model<Context>> {
|
||||||
self.contexts.iter().find_map(|context| {
|
self.contexts.iter().find_map(|context| {
|
||||||
let context = context.upgrade()?;
|
let context = context.upgrade()?;
|
||||||
if context.read(cx).id() == id {
|
if context.read(cx).id() == id {
|
||||||
|
|
|
@ -600,6 +600,9 @@ impl Server {
|
||||||
.add_request_handler(user_handler(
|
.add_request_handler(user_handler(
|
||||||
forward_mutating_project_request::<proto::OpenContext>,
|
forward_mutating_project_request::<proto::OpenContext>,
|
||||||
))
|
))
|
||||||
|
.add_request_handler(user_handler(
|
||||||
|
forward_mutating_project_request::<proto::CreateContext>,
|
||||||
|
))
|
||||||
.add_request_handler(user_handler(
|
.add_request_handler(user_handler(
|
||||||
forward_mutating_project_request::<proto::SynchronizeContexts>,
|
forward_mutating_project_request::<proto::SynchronizeContexts>,
|
||||||
))
|
))
|
||||||
|
|
|
@ -199,7 +199,7 @@ message Envelope {
|
||||||
StreamCompleteWithLanguageModel stream_complete_with_language_model = 228;
|
StreamCompleteWithLanguageModel stream_complete_with_language_model = 228;
|
||||||
StreamCompleteWithLanguageModelResponse stream_complete_with_language_model_response = 229;
|
StreamCompleteWithLanguageModelResponse stream_complete_with_language_model_response = 229;
|
||||||
CountLanguageModelTokens count_language_model_tokens = 230;
|
CountLanguageModelTokens count_language_model_tokens = 230;
|
||||||
CountLanguageModelTokensResponse count_language_model_tokens_response = 231; // current max
|
CountLanguageModelTokensResponse count_language_model_tokens_response = 231;
|
||||||
GetCachedEmbeddings get_cached_embeddings = 189;
|
GetCachedEmbeddings get_cached_embeddings = 189;
|
||||||
GetCachedEmbeddingsResponse get_cached_embeddings_response = 190;
|
GetCachedEmbeddingsResponse get_cached_embeddings_response = 190;
|
||||||
ComputeEmbeddings compute_embeddings = 191;
|
ComputeEmbeddings compute_embeddings = 191;
|
||||||
|
@ -255,6 +255,8 @@ message Envelope {
|
||||||
AdvertiseContexts advertise_contexts = 211;
|
AdvertiseContexts advertise_contexts = 211;
|
||||||
OpenContext open_context = 212;
|
OpenContext open_context = 212;
|
||||||
OpenContextResponse open_context_response = 213;
|
OpenContextResponse open_context_response = 213;
|
||||||
|
CreateContext create_context = 232;
|
||||||
|
CreateContextResponse create_context_response = 233; // current max
|
||||||
UpdateContext update_context = 214;
|
UpdateContext update_context = 214;
|
||||||
SynchronizeContexts synchronize_contexts = 215;
|
SynchronizeContexts synchronize_contexts = 215;
|
||||||
SynchronizeContextsResponse synchronize_contexts_response = 216;
|
SynchronizeContextsResponse synchronize_contexts_response = 216;
|
||||||
|
@ -2381,6 +2383,15 @@ message OpenContextResponse {
|
||||||
Context context = 1;
|
Context context = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message CreateContext {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateContextResponse {
|
||||||
|
string context_id = 1;
|
||||||
|
Context context = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message UpdateContext {
|
message UpdateContext {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
string context_id = 2;
|
string context_id = 2;
|
||||||
|
|
|
@ -398,6 +398,8 @@ messages!(
|
||||||
(AdvertiseContexts, Foreground),
|
(AdvertiseContexts, Foreground),
|
||||||
(OpenContext, Foreground),
|
(OpenContext, Foreground),
|
||||||
(OpenContextResponse, Foreground),
|
(OpenContextResponse, Foreground),
|
||||||
|
(CreateContext, Foreground),
|
||||||
|
(CreateContextResponse, Foreground),
|
||||||
(UpdateContext, Foreground),
|
(UpdateContext, Foreground),
|
||||||
(SynchronizeContexts, Foreground),
|
(SynchronizeContexts, Foreground),
|
||||||
(SynchronizeContextsResponse, Foreground),
|
(SynchronizeContextsResponse, Foreground),
|
||||||
|
@ -523,6 +525,7 @@ request_messages!(
|
||||||
(RenameDevServer, Ack),
|
(RenameDevServer, Ack),
|
||||||
(RestartLanguageServers, Ack),
|
(RestartLanguageServers, Ack),
|
||||||
(OpenContext, OpenContextResponse),
|
(OpenContext, OpenContextResponse),
|
||||||
|
(CreateContext, CreateContextResponse),
|
||||||
(SynchronizeContexts, SynchronizeContextsResponse),
|
(SynchronizeContexts, SynchronizeContextsResponse),
|
||||||
(AddWorktree, AddWorktreeResponse),
|
(AddWorktree, AddWorktreeResponse),
|
||||||
);
|
);
|
||||||
|
@ -589,6 +592,7 @@ entity_messages!(
|
||||||
LspExtExpandMacro,
|
LspExtExpandMacro,
|
||||||
AdvertiseContexts,
|
AdvertiseContexts,
|
||||||
OpenContext,
|
OpenContext,
|
||||||
|
CreateContext,
|
||||||
UpdateContext,
|
UpdateContext,
|
||||||
SynchronizeContexts,
|
SynchronizeContexts,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue