diff --git a/src/template.rs b/src/template.rs index 1cda522..fe98944 100644 --- a/src/template.rs +++ b/src/template.rs @@ -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, } // --- 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 { + 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 { + 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 { let mut items = Vec::new(); @@ -73,10 +152,12 @@ pub async fn list_all_templates() -> Vec { 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::(&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 { // 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; diff --git a/web/src/api.ts b/web/src/api.ts index 67d0054..bd4aab2 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -44,7 +44,7 @@ export const api = { }), listTemplates: () => - request<{ id: string; name: string; description: string }[]>('/templates'), + request<{ id: string; name: string; description: string; examples: { label: string; text: string }[] }[]>('/templates'), listSteps: (workflowId: string) => request(`/workflows/${workflowId}/steps`), diff --git a/web/src/components/CreateForm.vue b/web/src/components/CreateForm.vue index 875a755..abc1238 100644 --- a/web/src/components/CreateForm.vue +++ b/web/src/components/CreateForm.vue @@ -1,5 +1,5 @@ @@ -39,6 +76,32 @@ function onSubmit() { @click="requirement = Array.isArray(ex.text) ? ex.text.join('\n') : ex.text" >{{ ex.label }} +
+ + {{ ex.label }} +
+
+ + {{ rt.name }} +
+
+ + +