Detailed changes
@@ -377,6 +377,7 @@ dependencies = [
"anyhow",
"assets",
"assistant_tooling",
+ "chrono",
"client",
"collections",
"editor",
@@ -395,6 +396,7 @@ dependencies = [
"picker",
"project",
"rand 0.8.5",
+ "regex",
"release_channel",
"rich_text",
"schemars",
@@ -19,6 +19,7 @@ stories = ["dep:story"]
anyhow.workspace = true
assistant_tooling.workspace = true
client.workspace = true
+chrono.workspace = true
collections.workspace = true
editor.workspace = true
feature_flags.workspace = true
@@ -32,6 +33,7 @@ nanoid.workspace = true
open_ai.workspace = true
picker.workspace = true
project.workspace = true
+regex.workspace = true
rich_text.workspace = true
schemars.workspace = true
semantic_index.workspace = true
@@ -1,6 +1,16 @@
+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;
@@ -33,25 +43,48 @@ pub struct SavedAssistantMessagePart {
pub tool_calls: Vec<SavedToolFunctionCall>,
}
-/// Returns a list of placeholder conversations for mocking the UI.
-///
-/// Once we have real saved conversations to pull from we can use those instead.
-pub fn placeholder_conversations() -> Vec<SavedConversation> {
- vec![
- SavedConversation {
- version: "0.3.0".to_string(),
- title: "How to get a list of exported functions in an Erlang module".to_string(),
- messages: vec![],
- },
- SavedConversation {
- version: "0.3.0".to_string(),
- title: "7 wonders of the ancient world".to_string(),
- messages: vec![],
- },
- SavedConversation {
- version: "0.3.0".to_string(),
- title: "Size difference between u8 and a reference to u8 in Rust".to_string(),
- messages: vec![],
- },
- ]
+pub struct SavedConversationMetadata {
+ pub title: String,
+ pub path: PathBuf,
+ pub mtime: chrono::DateTime<chrono::Local>,
+}
+
+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::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)
+ }
}
@@ -7,7 +7,7 @@ use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ModalView, Workspace};
-use crate::saved_conversation::{self, SavedConversation};
+use crate::saved_conversation::SavedConversationMetadata;
use crate::ToggleSavedConversations;
pub struct SavedConversationPicker {
@@ -27,10 +27,26 @@ impl FocusableView for SavedConversationPicker {
impl SavedConversationPicker {
pub fn register(workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, _: &ToggleSavedConversations, cx| {
- workspace.toggle_modal(cx, move |cx| {
- let delegate = SavedConversationPickerDelegate::new(cx.view().downgrade());
- Self::new(delegate, cx)
- });
+ let fs = workspace.project().read(cx).fs().clone();
+
+ cx.spawn(|workspace, mut cx| async move {
+ let saved_conversations = SavedConversationMetadata::list(fs).await?;
+
+ cx.update(|cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.toggle_modal(cx, move |cx| {
+ let delegate = SavedConversationPickerDelegate::new(
+ cx.view().downgrade(),
+ saved_conversations,
+ );
+ Self::new(delegate, cx)
+ });
+ })
+ })??;
+
+ anyhow::Ok(())
+ })
+ .detach_and_log_err(cx);
});
}
@@ -48,14 +64,16 @@ impl Render for SavedConversationPicker {
pub struct SavedConversationPickerDelegate {
view: WeakView<SavedConversationPicker>,
- saved_conversations: Vec<SavedConversation>,
+ saved_conversations: Vec<SavedConversationMetadata>,
selected_index: usize,
matches: Vec<StringMatch>,
}
impl SavedConversationPickerDelegate {
- pub fn new(weak_view: WeakView<SavedConversationPicker>) -> Self {
- let saved_conversations = saved_conversation::placeholder_conversations();
+ pub fn new(
+ weak_view: WeakView<SavedConversationPicker>,
+ saved_conversations: Vec<SavedConversationMetadata>,
+ ) -> Self {
let matches = saved_conversations
.iter()
.map(|conversation| StringMatch {