assistant2: Add ability to delete past prompt editors (#25667)

Marshall Bowers created

This PR adds the ability to delete past prompt editors in Assistant 2,
the same way you can with threads.

Release Notes:

- N/A

Change summary

crates/assistant2/src/assistant_panel.rs             |  6 ++
crates/assistant2/src/thread_history.rs              | 37 +++++++++++--
crates/assistant_context_editor/src/context_store.rs | 34 ++++++++++++
3 files changed, 70 insertions(+), 7 deletions(-)

Detailed changes

crates/assistant2/src/assistant_panel.rs 🔗

@@ -458,6 +458,12 @@ impl AssistantPanel {
     pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
         self.context_editor.clone()
     }
+
+    pub(crate) fn delete_context(&mut self, path: PathBuf, cx: &mut Context<Self>) {
+        self.context_store
+            .update(cx, |this, cx| this.delete_local_context(path, cx))
+            .detach_and_log_err(cx);
+    }
 }
 
 impl Focusable for AssistantPanel {

crates/assistant2/src/thread_history.rs 🔗

@@ -134,7 +134,13 @@ impl ThreadHistory {
                         })
                         .ok();
                 }
-                HistoryEntry::Context(_context) => {}
+                HistoryEntry::Context(context) => {
+                    self.assistant_panel
+                        .update(cx, |this, cx| {
+                            this.delete_context(context.path.clone(), cx);
+                        })
+                        .ok();
+                }
             }
 
             cx.notify();
@@ -344,11 +350,30 @@ impl RenderOnce for PastContext {
         .spacing(ListItemSpacing::Sparse)
         .child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
         .end_slot(
-            h_flex().gap_1p5().child(
-                Label::new(context_timestamp)
-                    .color(Color::Muted)
-                    .size(LabelSize::XSmall),
-            ),
+            h_flex()
+                .gap_1p5()
+                .child(
+                    Label::new(context_timestamp)
+                        .color(Color::Muted)
+                        .size(LabelSize::XSmall),
+                )
+                .child(
+                    IconButton::new("delete", IconName::TrashAlt)
+                        .shape(IconButtonShape::Square)
+                        .icon_size(IconSize::XSmall)
+                        .tooltip(Tooltip::text("Delete Prompt Editor"))
+                        .on_click({
+                            let assistant_panel = self.assistant_panel.clone();
+                            let path = self.context.path.clone();
+                            move |_event, _window, cx| {
+                                assistant_panel
+                                    .update(cx, |this, cx| {
+                                        this.delete_context(path.clone(), cx);
+                                    })
+                                    .ok();
+                            }
+                        }),
+                ),
         )
         .on_click({
             let assistant_panel = self.assistant_panel.clone();

crates/assistant_context_editor/src/context_store.rs 🔗

@@ -9,7 +9,7 @@ use clock::ReplicaId;
 use collections::HashMap;
 use context_server::manager::ContextServerManager;
 use context_server::ContextServerFactoryRegistry;
-use fs::Fs;
+use fs::{Fs, RemoveOptions};
 use futures::StreamExt;
 use fuzzy::StringMatchCandidate;
 use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
@@ -475,6 +475,38 @@ impl ContextStore {
         })
     }
 
+    pub fn delete_local_context(
+        &mut self,
+        path: PathBuf,
+        cx: &mut Context<Self>,
+    ) -> Task<Result<()>> {
+        let fs = self.fs.clone();
+
+        cx.spawn(|this, mut cx| async move {
+            fs.remove_file(
+                &path,
+                RemoveOptions {
+                    recursive: false,
+                    ignore_if_not_exists: true,
+                },
+            )
+            .await?;
+
+            this.update(&mut cx, |this, cx| {
+                this.contexts.retain(|context| {
+                    context
+                        .upgrade()
+                        .and_then(|context| context.read(cx).path())
+                        != Some(&path)
+                });
+                this.contexts_metadata
+                    .retain(|context| context.path != path);
+            })?;
+
+            Ok(())
+        })
+    }
+
     fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
         self.contexts.iter().find_map(|context| {
             let context = context.upgrade()?;