Scan conversations dir on assistant panel open and on changes

Nathan Sobo and Julia Risley created

Co-Authored-By: Julia Risley <julia@zed.dev>

Change summary

crates/ai/src/ai.rs        | 22 +++++++++++-----------
crates/ai/src/assistant.rs | 30 ++++++++++++++++++++++++++++++
2 files changed, 41 insertions(+), 11 deletions(-)

Detailed changes

crates/ai/src/ai.rs 🔗

@@ -3,7 +3,6 @@ mod assistant_settings;
 
 use anyhow::Result;
 pub use assistant::AssistantPanel;
-use chrono::{DateTime, Local};
 use fs::Fs;
 use futures::StreamExt;
 use gpui::AppContext;
@@ -32,10 +31,17 @@ struct SavedConversation {
     messages: Vec<RequestMessage>,
 }
 
-impl SavedConversation {
-    pub async fn list(fs: Arc<dyn Fs>) -> Result<Vec<SavedConversationMetadata>> {
-        let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
+struct SavedConversationMetadata {
+    title: String,
+    path: PathBuf,
+    mtime: SystemTime,
+}
+
+impl SavedConversationMetadata {
+    pub async fn list(fs: Arc<dyn Fs>) -> Result<Vec<Self>> {
+        fs.create_dir(&CONVERSATIONS_DIR).await?;
 
+        let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?;
         let mut conversations = Vec::<SavedConversationMetadata>::new();
         while let Some(path) = paths.next().await {
             let path = path?;
@@ -50,7 +56,7 @@ impl SavedConversation {
                 .zip(metadata)
             {
                 let title = re.replace(file_name, "");
-                conversations.push(SavedConversationMetadata {
+                conversations.push(Self {
                     title: title.into_owned(),
                     path,
                     mtime: metadata.mtime,
@@ -62,12 +68,6 @@ impl SavedConversation {
     }
 }
 
-struct SavedConversationMetadata {
-    title: String,
-    path: PathBuf,
-    mtime: SystemTime,
-}
-
 #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
 struct RequestMessage {
     role: Role,

crates/ai/src/assistant.rs 🔗

@@ -1,6 +1,7 @@
 use crate::{
     assistant_settings::{AssistantDockPosition, AssistantSettings},
     OpenAIRequest, OpenAIResponseStreamEvent, RequestMessage, Role, SavedConversation,
+    SavedConversationMetadata,
 };
 use anyhow::{anyhow, Result};
 use chrono::{DateTime, Local};
@@ -105,6 +106,8 @@ pub struct AssistantPanel {
     languages: Arc<LanguageRegistry>,
     fs: Arc<dyn Fs>,
     subscriptions: Vec<Subscription>,
+    saved_conversations: Vec<SavedConversationMetadata>,
+    _watch_saved_conversations: Task<Result<()>>,
 }
 
 impl AssistantPanel {
@@ -113,6 +116,12 @@ impl AssistantPanel {
         cx: AsyncAppContext,
     ) -> Task<Result<ViewHandle<Self>>> {
         cx.spawn(|mut cx| async move {
+            let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
+            let saved_conversations = SavedConversationMetadata::list(fs.clone())
+                .await
+                .log_err()
+                .unwrap_or_default();
+
             // TODO: deserialize state.
             workspace.update(&mut cx, |workspace, cx| {
                 cx.add_view::<Self, _>(|cx| {
@@ -171,6 +180,25 @@ impl AssistantPanel {
                         pane
                     });
 
+                    const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
+                    let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
+                        let mut events = fs
+                            .watch(&CONVERSATIONS_DIR, CONVERSATION_WATCH_DURATION)
+                            .await;
+                        while events.next().await.is_some() {
+                            let saved_conversations = SavedConversationMetadata::list(fs.clone())
+                                .await
+                                .log_err()
+                                .unwrap_or_default();
+                            this.update(&mut cx, |this, _| {
+                                this.saved_conversations = saved_conversations
+                            })
+                            .ok();
+                        }
+
+                        anyhow::Ok(())
+                    });
+
                     let mut this = Self {
                         pane,
                         api_key: Rc::new(RefCell::new(None)),
@@ -181,6 +209,8 @@ impl AssistantPanel {
                         width: None,
                         height: None,
                         subscriptions: Default::default(),
+                        saved_conversations,
+                        _watch_saved_conversations,
                     };
 
                     let mut old_dock_position = this.position(cx);