feat: template examples + recent templates quick buttons
- Add TemplateExample struct and examples scanning (local dir + git repo) - Exclude examples/ from copy_dir_recursive - Frontend: recent templates (localStorage), template-specific example buttons
This commit is contained in:
@@ -22,11 +22,18 @@ pub struct LoadedTemplate {
|
||||
pub kb_files: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct TemplateExample {
|
||||
pub label: String,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct TemplateListItem {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub examples: Vec<TemplateExample>,
|
||||
}
|
||||
|
||||
// --- Template directories ---
|
||||
@@ -54,6 +61,78 @@ pub fn templates_dir() -> &'static str {
|
||||
|
||||
// --- Scanning ---
|
||||
|
||||
/// Scan a local examples/ directory for .md files.
|
||||
async fn scan_examples_dir(dir: &Path) -> Vec<TemplateExample> {
|
||||
let mut examples = Vec::new();
|
||||
let mut entries = match tokio::fs::read_dir(dir).await {
|
||||
Ok(e) => e,
|
||||
Err(_) => return examples,
|
||||
};
|
||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|e| e.to_str()) != Some("md") {
|
||||
continue;
|
||||
}
|
||||
let label = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("example")
|
||||
.to_string();
|
||||
if let Ok(text) = tokio::fs::read_to_string(&path).await {
|
||||
examples.push(TemplateExample { label, text });
|
||||
}
|
||||
}
|
||||
examples.sort_by(|a, b| a.label.cmp(&b.label));
|
||||
examples
|
||||
}
|
||||
|
||||
/// Scan examples from a git repo ref via `git ls-tree` + `git show`.
|
||||
async fn scan_examples_git(repo: &Path, ref_name: &str, template_path: &str) -> Vec<TemplateExample> {
|
||||
let examples_prefix = if template_path.is_empty() {
|
||||
"examples/".to_string()
|
||||
} else {
|
||||
format!("{}/examples/", template_path)
|
||||
};
|
||||
|
||||
let tree_output = match tokio::process::Command::new("git")
|
||||
.args(["ls-tree", "--name-only", ref_name, &examples_prefix])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).to_string(),
|
||||
_ => return Vec::new(),
|
||||
};
|
||||
|
||||
let mut examples = Vec::new();
|
||||
for file_path in tree_output.lines() {
|
||||
let file_path = file_path.trim();
|
||||
if !file_path.ends_with(".md") {
|
||||
continue;
|
||||
}
|
||||
let label = Path::new(file_path)
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("example")
|
||||
.to_string();
|
||||
|
||||
let show_ref = format!("{}:{}", ref_name, file_path);
|
||||
if let Ok(o) = tokio::process::Command::new("git")
|
||||
.args(["show", &show_ref])
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
if o.status.success() {
|
||||
let text = String::from_utf8_lossy(&o.stdout).to_string();
|
||||
examples.push(TemplateExample { label, text });
|
||||
}
|
||||
}
|
||||
}
|
||||
examples.sort_by(|a, b| a.label.cmp(&b.label));
|
||||
examples
|
||||
}
|
||||
|
||||
/// List all templates from both built-in and repo (all branches).
|
||||
pub async fn list_all_templates() -> Vec<TemplateListItem> {
|
||||
let mut items = Vec::new();
|
||||
@@ -73,10 +152,12 @@ pub async fn list_all_templates() -> Vec<TemplateListItem> {
|
||||
let meta_path = entry.path().join("template.json");
|
||||
if let Ok(data) = tokio::fs::read_to_string(&meta_path).await {
|
||||
if let Ok(info) = serde_json::from_str::<TemplateInfo>(&data) {
|
||||
let examples = scan_examples_dir(&entry.path().join("examples")).await;
|
||||
items.push(TemplateListItem {
|
||||
id,
|
||||
name: info.name,
|
||||
description: info.description,
|
||||
examples,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -159,11 +240,13 @@ async fn scan_repo_all_branches(repo: &Path) -> Vec<TemplateListItem> {
|
||||
|
||||
// Try to read template.json via git show
|
||||
let (name, description) = read_git_file_json(repo, line, template_path).await;
|
||||
let examples = scan_examples_git(repo, line, template_path).await;
|
||||
|
||||
items.push(TemplateListItem {
|
||||
id: template_id.clone(),
|
||||
name: name.unwrap_or_else(|| template_id.clone()),
|
||||
description: description.unwrap_or_default(),
|
||||
examples,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -379,6 +462,7 @@ async fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> {
|
||||
&& (name_str == "template.json"
|
||||
|| name_str == "tools"
|
||||
|| name_str == "kb"
|
||||
|| name_str == "examples"
|
||||
|| name_str == "INSTRUCTIONS.md")
|
||||
{
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user