assistant2: List saved conversations from disk (#11627)

This PR updates the saved conversation picker to use a list of
conversations retrieved from disk instead of the static placeholder
values.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-05-09 16:17:07 -04:00 committed by GitHub
parent 8b5a0cff10
commit c73ef1a5f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 84 additions and 29 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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)
}
}

View File

@ -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 {