use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata}; use anyhow::{anyhow, Result}; use collections::HashMap; use fs::Fs; use futures::StreamExt; use regex::Regex; use serde::{Deserialize, Serialize}; use std::{ cmp::Reverse, ffi::OsStr, path::{Path, PathBuf}, sync::Arc, }; use util::paths::CONVERSATIONS_DIR; #[derive(Serialize, Deserialize)] pub struct SavedMessage { pub id: MessageId, pub start: usize, } #[derive(Serialize, Deserialize)] pub struct SavedConversation { pub id: Option, pub zed: String, pub version: String, pub text: String, pub messages: Vec, pub message_metadata: HashMap, pub summary: String, } impl SavedConversation { pub const VERSION: &'static str = "0.2.0"; pub async fn load(path: &Path, fs: &dyn Fs) -> Result { let saved_conversation = fs.load(path).await?; let saved_conversation_json = serde_json::from_str::(&saved_conversation)?; match saved_conversation_json .get("version") .ok_or_else(|| anyhow!("version not found"))? { serde_json::Value::String(version) => match version.as_str() { Self::VERSION => Ok(serde_json::from_value::(saved_conversation_json)?), "0.1.0" => { let saved_conversation = serde_json::from_value::(saved_conversation_json)?; Ok(Self { id: saved_conversation.id, zed: saved_conversation.zed, version: saved_conversation.version, text: saved_conversation.text, messages: saved_conversation.messages, message_metadata: saved_conversation.message_metadata, summary: saved_conversation.summary, }) } _ => Err(anyhow!( "unrecognized saved conversation version: {}", version )), }, _ => Err(anyhow!("version not found on saved conversation")), } } } #[derive(Serialize, Deserialize)] struct SavedConversationV0_1_0 { id: Option, zed: String, version: String, text: String, messages: Vec, message_metadata: HashMap, summary: String, api_url: Option, model: OpenAiModel, } 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.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 new 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) } }