mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 18:41:56 +03:00
Rework prompt frontmatter (#12262)
Moved some things around so prompts now always have front-matter to return, either by creating a prompt with default front-matter, or bailing earlier on importing the prompt to the library. In the future we'll improve visibility of malformed prompts in the `prompts` folder in the prompt manager UI. Fixes: - Prompts inserted with the `/prompt` command now only include their body, not the entire file including metadata. - Prompts with an invalid title will now show "Untitled prompt" instead of an empty line. Release Notes: - N/A
This commit is contained in:
parent
461e7d00a6
commit
800c1ba916
@ -19,7 +19,7 @@ pub struct StaticPromptFrontmatter {
|
|||||||
impl Default for StaticPromptFrontmatter {
|
impl Default for StaticPromptFrontmatter {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
title: "New Prompt".to_string(),
|
title: "Untitled Prompt".to_string(),
|
||||||
version: "1.0".to_string(),
|
version: "1.0".to_string(),
|
||||||
author: "No Author".to_string(),
|
author: "No Author".to_string(),
|
||||||
languages: vec!["*".to_string()],
|
languages: vec!["*".to_string()],
|
||||||
@ -28,37 +28,7 @@ impl Default for StaticPromptFrontmatter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticPromptFrontmatter {
|
/// A static prompt that can be loaded into the prompt library
|
||||||
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
|
|
||||||
/// from Markdown with a frontmatter header
|
/// from Markdown with a frontmatter header
|
||||||
///
|
///
|
||||||
/// Examples:
|
/// Examples:
|
||||||
@ -92,95 +62,69 @@ impl StaticPromptFrontmatter {
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub struct StaticPrompt {
|
pub struct StaticPrompt {
|
||||||
|
#[serde(skip)]
|
||||||
|
metadata: StaticPromptFrontmatter,
|
||||||
content: String,
|
content: String,
|
||||||
file_name: Option<String>,
|
file_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticPrompt {
|
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 {
|
StaticPrompt {
|
||||||
content,
|
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 {
|
impl StaticPrompt {
|
||||||
// pub fn update(&mut self, contents: String) -> &mut Self {
|
|
||||||
// self.content = contents;
|
|
||||||
// self
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Sets the file name of the prompt
|
/// 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.file_name = Some(file_name);
|
||||||
self
|
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
|
/// Returns the prompt's content
|
||||||
pub fn content(&self) -> &String {
|
pub fn content(&self) -> &String {
|
||||||
&self.content
|
&self.content
|
||||||
}
|
}
|
||||||
fn parse(&self) -> anyhow::Result<(StaticPromptFrontmatter, String)> {
|
|
||||||
let matter = Matter::<YAML>::new();
|
/// Returns the prompt's metadata
|
||||||
let result = matter.parse(self.content.as_str());
|
pub fn _metadata(&self) -> &StaticPromptFrontmatter {
|
||||||
match result.data {
|
&self.metadata
|
||||||
Some(data) => {
|
|
||||||
let front_matter: StaticPromptFrontmatter = data.deserialize()?;
|
|
||||||
let body = result.content;
|
|
||||||
Ok((front_matter, body))
|
|
||||||
}
|
|
||||||
None => Err(anyhow::anyhow!("Failed to parse frontmatter")),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metadata(&self) -> Option<StaticPromptFrontmatter> {
|
/// Returns the prompt's title
|
||||||
self.parse().ok().map(|(front_matter, _)| front_matter)
|
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 collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
|
|
||||||
|
use gray_matter::{engine::YAML, Matter};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
@ -119,6 +120,17 @@ impl PromptLibrary {
|
|||||||
|
|
||||||
while let Some(prompt_path) = prompt_paths.next().await {
|
while let Some(prompt_path) = prompt_paths.next().await {
|
||||||
let prompt_path = prompt_path.with_context(|| "Failed to read prompt path")?;
|
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
|
if !fs.is_file(&prompt_path).await
|
||||||
|| prompt_path.extension().and_then(|ext| ext.to_str()) != Some("md")
|
|| prompt_path.extension().and_then(|ext| ext.to_str()) != Some("md")
|
||||||
@ -130,13 +142,17 @@ impl PromptLibrary {
|
|||||||
.load(&prompt_path)
|
.load(&prompt_path)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to load prompt {:?}", prompt_path))?;
|
.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() {
|
// Check that the prompt is valid
|
||||||
let file_name = file_name.to_string_lossy().into_owned();
|
let matter = Matter::<YAML>::new();
|
||||||
static_prompt.file_name(file_name);
|
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 state = self.state.get_mut();
|
||||||
|
|
||||||
let id = Uuid::new_v4();
|
let id = Uuid::new_v4();
|
||||||
|
@ -321,7 +321,7 @@ impl PickerDelegate for PromptManagerDelegate {
|
|||||||
.inset(true)
|
.inset(true)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.selected(selected)
|
.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()
|
.prompts()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(ix, prompt)| {
|
.map(|(ix, prompt)| StringMatchCandidate::new(ix, prompt.1.title().to_string()))
|
||||||
prompt
|
|
||||||
.1
|
|
||||||
.title()
|
|
||||||
.map(|title| StringMatchCandidate::new(ix, title.into()))
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let matches = fuzzy::match_strings(
|
let matches = fuzzy::match_strings(
|
||||||
&candidates,
|
&candidates,
|
||||||
@ -86,11 +81,10 @@ impl SlashCommand for PromptSlashCommand {
|
|||||||
let prompt = library
|
let prompt = library
|
||||||
.prompts()
|
.prompts()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|prompt| prompt.1.title().map(|title| (title, prompt)))
|
.find(|prompt| &prompt.1.title().to_string() == &title)
|
||||||
.find(|(t, _)| t == &title)
|
|
||||||
.with_context(|| format!("no prompt found with title {:?}", title))?
|
.with_context(|| format!("no prompt found with title {:?}", title))?
|
||||||
.1;
|
.1;
|
||||||
Ok(prompt.1.content().to_owned())
|
Ok(prompt.body())
|
||||||
});
|
});
|
||||||
SlashCommandInvocation {
|
SlashCommandInvocation {
|
||||||
output,
|
output,
|
||||||
|
Loading…
Reference in New Issue
Block a user