@@ -19,7 +19,7 @@ pub struct StaticPromptFrontmatter {
impl Default for StaticPromptFrontmatter {
fn default() -> Self {
Self {
- title: "New Prompt".to_string(),
+ title: "Untitled Prompt".to_string(),
version: "1.0".to_string(),
author: "No Author".to_string(),
languages: vec!["*".to_string()],
@@ -28,37 +28,7 @@ impl Default for StaticPromptFrontmatter {
}
}
-impl StaticPromptFrontmatter {
- pub fn title(&self) -> SharedString {
- self.title.clone().into()
- }
-
- // pub fn version(&self) -> SharedString {
- // self.version.clone().into()
- // }
-
- // pub fn author(&self) -> SharedString {
- // self.author.clone().into()
- // }
-
- // pub fn languages(&self) -> Vec<SharedString> {
- // self.languages
- // .clone()
- // .into_iter()
- // .map(|s| s.into())
- // .collect()
- // }
-
- // pub fn dependencies(&self) -> Vec<SharedString> {
- // self.dependencies
- // .clone()
- // .into_iter()
- // .map(|s| s.into())
- // .collect()
- // }
-}
-
-/// A statuc prompt that can be loaded into the prompt library
+/// A static prompt that can be loaded into the prompt library
/// from Markdown with a frontmatter header
///
/// Examples:
@@ -92,95 +62,69 @@ impl StaticPromptFrontmatter {
/// ```
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct StaticPrompt {
+ #[serde(skip)]
+ metadata: StaticPromptFrontmatter,
content: String,
file_name: Option<String>,
}
impl StaticPrompt {
- pub fn new(content: String) -> Self {
+ pub fn new(content: String, file_name: Option<String>) -> Self {
+ let matter = Matter::<YAML>::new();
+ let result = matter.parse(&content);
+
+ let metadata = result
+ .data
+ .map_or_else(
+ || Err(anyhow::anyhow!("Failed to parse frontmatter")),
+ |data| {
+ let front_matter: StaticPromptFrontmatter = data.deserialize()?;
+ Ok(front_matter)
+ },
+ )
+ .unwrap_or_else(|e| {
+ if let Some(file_name) = &file_name {
+ log::error!("Failed to parse frontmatter for {}: {}", file_name, e);
+ } else {
+ log::error!("Failed to parse frontmatter: {}", e);
+ }
+ StaticPromptFrontmatter::default()
+ });
+
StaticPrompt {
content,
- file_name: None,
+ file_name,
+ metadata,
}
}
-
- pub fn title(&self) -> Option<SharedString> {
- self.metadata().map(|m| m.title())
- }
-
- // pub fn version(&self) -> Option<SharedString> {
- // self.metadata().map(|m| m.version())
- // }
-
- // pub fn author(&self) -> Option<SharedString> {
- // self.metadata().map(|m| m.author())
- // }
-
- // pub fn languages(&self) -> Vec<SharedString> {
- // self.metadata().map(|m| m.languages()).unwrap_or_default()
- // }
-
- // pub fn dependencies(&self) -> Vec<SharedString> {
- // self.metadata()
- // .map(|m| m.dependencies())
- // .unwrap_or_default()
- // }
-
- // pub fn load(fs: Arc<Fs>, file_name: String) -> anyhow::Result<Self> {
- // todo!()
- // }
-
- // pub fn save(&self, fs: Arc<Fs>) -> anyhow::Result<()> {
- // todo!()
- // }
-
- // pub fn rename(&self, new_file_name: String, fs: Arc<Fs>) -> anyhow::Result<()> {
- // todo!()
- // }
}
impl StaticPrompt {
- // pub fn update(&mut self, contents: String) -> &mut Self {
- // self.content = contents;
- // self
- // }
-
/// Sets the file name of the prompt
- pub fn file_name(&mut self, file_name: String) -> &mut Self {
+ pub fn _file_name(&mut self, file_name: String) -> &mut Self {
self.file_name = Some(file_name);
self
}
- /// Sets the file name of the prompt based on the title
- // pub fn file_name_from_title(&mut self) -> &mut Self {
- // if let Some(title) = self.title() {
- // let file_name = title.to_lowercase().replace(" ", "_");
- // if !file_name.is_empty() {
- // self.file_name = Some(file_name);
- // }
- // }
- // self
- // }
-
/// Returns the prompt's content
pub fn content(&self) -> &String {
&self.content
}
- fn parse(&self) -> anyhow::Result<(StaticPromptFrontmatter, String)> {
- let matter = Matter::<YAML>::new();
- let result = matter.parse(self.content.as_str());
- match result.data {
- Some(data) => {
- let front_matter: StaticPromptFrontmatter = data.deserialize()?;
- let body = result.content;
- Ok((front_matter, body))
- }
- None => Err(anyhow::anyhow!("Failed to parse frontmatter")),
- }
+
+ /// Returns the prompt's metadata
+ pub fn _metadata(&self) -> &StaticPromptFrontmatter {
+ &self.metadata
}
- pub fn metadata(&self) -> Option<StaticPromptFrontmatter> {
- self.parse().ok().map(|(front_matter, _)| front_matter)
+ /// Returns the prompt's title
+ pub fn title(&self) -> SharedString {
+ self.metadata.title.clone().into()
+ }
+
+ pub fn body(&self) -> String {
+ let matter = Matter::<YAML>::new();
+ let result = matter.parse(self.content.as_str());
+ result.content.clone()
}
}
@@ -2,6 +2,7 @@ use anyhow::Context;
use collections::HashMap;
use fs::Fs;
+use gray_matter::{engine::YAML, Matter};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use smol::stream::StreamExt;
@@ -119,6 +120,17 @@ impl PromptLibrary {
while let Some(prompt_path) = prompt_paths.next().await {
let prompt_path = prompt_path.with_context(|| "Failed to read prompt path")?;
+ let file_name_lossy = if prompt_path.file_name().is_some() {
+ Some(
+ prompt_path
+ .file_name()
+ .unwrap()
+ .to_string_lossy()
+ .to_string(),
+ )
+ } else {
+ None
+ };
if !fs.is_file(&prompt_path).await
|| prompt_path.extension().and_then(|ext| ext.to_str()) != Some("md")
@@ -130,13 +142,17 @@ impl PromptLibrary {
.load(&prompt_path)
.await
.with_context(|| format!("Failed to load prompt {:?}", prompt_path))?;
- let mut static_prompt = StaticPrompt::new(json);
- if let Some(file_name) = prompt_path.file_name() {
- let file_name = file_name.to_string_lossy().into_owned();
- static_prompt.file_name(file_name);
+ // Check that the prompt is valid
+ let matter = Matter::<YAML>::new();
+ let result = matter.parse(&json);
+ if result.data.is_none() {
+ log::warn!("Invalid prompt: {:?}", prompt_path);
+ continue;
}
+ let static_prompt = StaticPrompt::new(json, file_name_lossy.clone());
+
let state = self.state.get_mut();
let id = Uuid::new_v4();
@@ -321,7 +321,7 @@ impl PickerDelegate for PromptManagerDelegate {
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
- .child(Label::new(prompt.title().unwrap_or_default().clone())),
+ .child(Label::new(prompt.title())),
)
}
}
@@ -43,12 +43,7 @@ impl SlashCommand for PromptSlashCommand {
.prompts()
.into_iter()
.enumerate()
- .filter_map(|(ix, prompt)| {
- prompt
- .1
- .title()
- .map(|title| StringMatchCandidate::new(ix, title.into()))
- })
+ .map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.1.title().to_string()))
.collect::<Vec<_>>();
let matches = fuzzy::match_strings(
&candidates,
@@ -86,11 +81,10 @@ impl SlashCommand for PromptSlashCommand {
let prompt = library
.prompts()
.into_iter()
- .filter_map(|prompt| prompt.1.title().map(|title| (title, prompt)))
- .find(|(t, _)| t == &title)
+ .find(|prompt| &prompt.1.title().to_string() == &title)
.with_context(|| format!("no prompt found with title {:?}", title))?
.1;
- Ok(prompt.1.content().to_owned())
+ Ok(prompt.body())
});
SlashCommandInvocation {
output,