use std::cmp::Reverse; use std::ffi::OsStr; use std::path::PathBuf; use std::sync::Arc; use anyhow::Result; use assistant_tooling::{SavedToolFunctionCall, SavedUserAttachment}; use fs::Fs; use futures::StreamExt; use gpui::SharedString; use regex::Regex; use serde::{Deserialize, Serialize}; use util::paths::CONVERSATIONS_DIR; use crate::MessageId; #[derive(Serialize, Deserialize)] pub struct SavedConversation { /// The schema version of the conversation. pub version: String, /// The title of the conversation, generated by the Assistant. pub title: String, pub messages: Vec, } #[derive(Serialize, Deserialize)] pub enum SavedChatMessage { User { id: MessageId, body: String, attachments: Vec, }, Assistant { id: MessageId, messages: Vec, error: Option, }, } #[derive(Serialize, Deserialize)] pub struct SavedAssistantMessagePart { pub body: SharedString, pub tool_calls: Vec, } pub struct SavedConversationMetadata { pub title: String, pub path: PathBuf, pub mtime: chrono::DateTime, } impl SavedConversationMetadata { pub async fn list(fs: Arc) -> Result> { fs.create_dir(&CONVERSATIONS_DIR).await?; let mut paths = fs.read_dir(&CONVERSATIONS_DIR).await?; let mut conversations = Vec::new(); while let Some(path) = paths.next().await { let path = path?; if path.extension() != Some(OsStr::new("json")) { continue; } let pattern = r" - \d+.zed.\d.\d.\d.json$"; let re = Regex::new(pattern).unwrap(); let metadata = fs.metadata(&path).await?; if let Some((file_name, metadata)) = path .file_name() .and_then(|name| name.to_str()) .zip(metadata) { // This is used to filter out conversations saved by the old assistant. if !re.is_match(file_name) { continue; } let title = re.replace(file_name, ""); conversations.push(Self { title: title.into_owned(), path, mtime: metadata.mtime.into(), }); } } conversations.sort_unstable_by_key(|conversation| Reverse(conversation.mtime)); Ok(conversations) } }