yggdrasil/api/
writing_guide.rs1#![allow(clippy::unused_unit, deprecated)]
3
4use crate::api::error::ApiError;
5use leptos::prelude::*;
6
7#[cfg(feature = "server")]
9pub const SKILL_SOURCE: &str = include_str!("../../public/skills/yggdrasil-writing/SKILL.md");
10pub const SKILL_URL: &str = "/skills/yggdrasil-writing/SKILL.md";
11
12#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
13pub struct WritingGuideData {
14 pub source: String,
15 pub html: String,
16 pub toc_html: String,
17 pub configs: [String; 3],
18 pub install_prompt: String,
19}
20
21#[cfg(feature = "server")]
23fn render_guide() -> crate::api::markdown::RenderedContent {
24 let body = SKILL_SOURCE
25 .strip_prefix("---\n")
26 .and_then(|text| text.split_once("\n---\n"))
27 .map(|(_, body)| body.trim_start())
28 .unwrap_or(SKILL_SOURCE);
29 let body = body.strip_prefix("# Yggdrasil 文章写作\n").unwrap_or(body);
31 crate::api::markdown::render_markdown_enhanced(body)
32}
33
34#[server(endpoint = "get_writing_guide", input = leptos::server_fn::codec::Json)]
35pub async fn get_writing_guide() -> Result<WritingGuideData, ApiError> {
36 static RENDERED: std::sync::LazyLock<crate::api::markdown::RenderedContent> =
37 std::sync::LazyLock::new(render_guide);
38 let base_url = crate::api::settings::runtime_security_settings()
39 .await
40 .app_base_url;
41 let base_url = if base_url.trim().is_empty() {
42 crate::mcp::config::base_url_from_env()
43 } else {
44 base_url
45 };
46 let configs = crate::mcp::config::generate_client_configs(&base_url, "YOUR_YGGDRASIL_TOKEN");
47 Ok(WritingGuideData {
48 source: SKILL_SOURCE.to_string(),
49 html: RENDERED.html.clone(),
50 toc_html: RENDERED.toc_html.clone(),
51 configs: [configs.codex_toml, configs.omp_json, configs.opencode_json],
52 install_prompt: format!(
53 "请从 {}{SKILL_URL} 下载完整的 SKILL.md,将 yggdrasil-writing 安装为用户全局 skill,保存到 ~/.agents/skills/yggdrasil-writing/SKILL.md(不要安装到项目目录),并确认当前 Agent 能发现该技能。",
54 base_url.trim_end_matches('/')
55 ),
56 })
57}
58
59#[cfg(all(test, feature = "server"))]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn guide_renders_documentation_without_activating_example_blocks() {
65 let rendered = render_guide();
66 assert!(!rendered.html.contains("description:"));
67 assert!(!rendered.html.contains("<h1"));
68 assert!(rendered.html.contains("<h2"));
69 assert!(rendered.html.contains("<table"));
70 assert!(rendered.html.contains("language-markdown"));
71 assert!(!rendered.html.contains("data-runnable=\"true\""));
72 assert!(!rendered.html.contains("mermaid-diagram"));
73 assert!(rendered.toc_html.contains("可运行代码块"));
74 assert!(SKILL_SOURCE.starts_with("---\nname: yggdrasil-writing\n"));
75 }
76}