Skip to main content

yggdrasil/api/
writing_guide.rs

1//! 公开写作指南。源码与下载共用 public 中的文件,不读取令牌或用户数据。
2#![allow(clippy::unused_unit, deprecated)]
3
4use crate::api::error::ApiError;
5use leptos::prelude::*;
6
7// 源码只嵌入服务端二进制,经 server function 下发,避免每位访客的 WASM 都带上全文。
8#[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/// Frontmatter 留在下载及复制的原文中;阅读版从 Markdown 正文开始。
22#[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    // 页面已有 h1,文档标题由面板的 h2 承担。
30    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}