diff --git a/crates/docs/src/lib.rs b/crates/docs/src/lib.rs index e6c3e364cf..e9d70a46df 100644 --- a/crates/docs/src/lib.rs +++ b/crates/docs/src/lib.rs @@ -41,6 +41,7 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) { struct Assets> { search_js: S, + llms_txt: S, styles_css: S, raw_template_html: S, } @@ -48,11 +49,13 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) { #[cfg(not(debug_assertions))] let assets = { let search_js = include_str!("./static/search.js"); + let llms_txt = include_str!("./static/llms.txt"); let styles_css = include_str!("./static/styles.css"); let raw_template_html = include_str!("./static/index.html"); Assets { search_js, + llms_txt, styles_css, raw_template_html, } @@ -66,11 +69,13 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) { // Read the assets from the filesystem let search_js = fs::read_to_string(static_dir.join("search.js")).unwrap(); + let llms_txt = fs::read_to_string(static_dir.join("llms.txt")).unwrap(); let styles_css = fs::read_to_string(static_dir.join("styles.css")).unwrap(); let raw_template_html = fs::read_to_string(static_dir.join("index.html")).unwrap(); Assets { search_js, + llms_txt, styles_css, raw_template_html, } @@ -80,6 +85,7 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) { // (The HTML requires more work!) for (file, contents) in [ ("search.js", assets.search_js), + ("llms.txt", assets.llms_txt), ("styles.css", assets.styles_css), ] { let dir = build_dir.join(file); @@ -118,6 +124,15 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) { render_search_type_ahead(exposed_module_docs.iter().map(|(_, docs)| docs)).as_str(), ); + { + let llms_txt = llm_prompt( + package_name.as_str(), + exposed_module_docs.iter().map(|(_, docs)| docs), + ); + fs::write(build_dir.join("llms.txt"), llms_txt) + .expect("TODO gracefully handle failing to write llms.txt"); + } + let all_exposed_symbols = { let mut set = VecSet::default(); @@ -539,6 +554,87 @@ fn render_search_type_ahead<'a, I: Iterator>(mod buf } +fn llm_prompt<'a, I: Iterator>( + package_name: &str, + modules: I, +) -> String { + let mut example_type_question_buf = String::new(); + let mut example_description_question_buf = String::new(); + let mut buf = String::new(); + buf.push_str(format!("# LLM Prompt for {}\n\n", package_name).as_str()); + buf.push_str("## Documentation\n\n"); + for module in modules { + let module_name = module.name.as_str(); + buf.push_str(format!("### {}\n\n", module_name).as_str()); + for entry in &module.entries { + if let DocEntry::DocDef(doc_def) = entry { + if module.exposed_symbols.contains(&doc_def.symbol) { + let mut doc_def_buf = String::new(); + doc_def_buf.push_str(format!("#### {}\n\n", doc_def.name).as_str()); + + doc_def_buf.push_str("**Type Annotation**\n\n"); + let mut annotation_buf = String::new(); + type_annotation_to_html( + 0, + &mut annotation_buf, + &doc_def.type_annotation, + false, + ); + + if !annotation_buf.is_empty() { + doc_def_buf.push_str("```roc\n"); + doc_def_buf.push_str(format!("{}\n", annotation_buf).as_str()); + doc_def_buf.push_str("```\n\n"); + } + + let mut description_buf = String::new(); + if let Some(docs) = &doc_def.docs { + doc_def_buf.push_str("**Description**\n\n"); + doc_def_buf.push_str(format!("{}\n", docs).as_str()); + description_buf.push_str(docs.as_str()); + } + + buf.push_str(doc_def_buf.as_str()); + + if example_type_question_buf.is_empty() && !annotation_buf.is_empty() { + example_type_question_buf.push_str("**Annotation Question Example**\n\n"); + example_type_question_buf.push_str("**Question:**\n"); + example_type_question_buf.push_str( + format!("What is the type definition for `{}`?\n\n", doc_def.name) + .as_str(), + ); + example_type_question_buf.push_str("**Response:**\n"); + example_type_question_buf + .push_str(format!("{}\n\n", annotation_buf).as_str()); + example_type_question_buf.push_str("**Source:**\n"); + example_description_question_buf.push_str("```md\n"); + example_type_question_buf + .push_str(format!("{}\n", annotation_buf).as_str()); + example_description_question_buf.push_str("```\n\n"); + } + + if example_description_question_buf.is_empty() && !description_buf.is_empty() { + example_description_question_buf + .push_str("**Description Question Example**\n\n"); + example_description_question_buf.push_str("**Question:**\n"); + example_description_question_buf + .push_str(format!("What does `{}` do?\n\n", doc_def.name).as_str()); + example_description_question_buf.push_str("**Response:**\n"); + example_description_question_buf + .push_str(format!("{}\n\n", description_buf).as_str()); + example_description_question_buf.push_str("**Source:**\n"); + example_description_question_buf.push_str("```md\n"); + example_description_question_buf + .push_str(format!("{}\n", doc_def_buf).as_str()); + example_description_question_buf.push_str("```\n\n"); + } + } + } + } + } + buf +} + pub fn load_module_for_docs(filename: PathBuf) -> LoadedModule { let arena = Bump::new(); let load_config = LoadConfig { diff --git a/crates/docs/src/static/index.html b/crates/docs/src/static/index.html index 7293685a38..e043e6e02d 100644 --- a/crates/docs/src/static/index.html +++ b/crates/docs/src/static/index.html @@ -55,6 +55,11 @@ +
diff --git a/crates/docs/src/static/llms.txt b/crates/docs/src/static/llms.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/docs/src/static/styles.css b/crates/docs/src/static/styles.css index 55d506d345..3b426ffd81 100644 --- a/crates/docs/src/static/styles.css +++ b/crates/docs/src/static/styles.css @@ -560,6 +560,20 @@ pre>samp { right: 30px; } +#llm-prompt-container { + background-color: var(--violet-bg); + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: initial; +} + +#llm-prompt-copy-button:hover { + border-color: var(--link-hover-color); + color: var(--link-hover-color); +} + .builtins-tip { padding: 1em; font-style: italic; @@ -598,7 +612,7 @@ pre>samp { @media only screen and (max-device-width: 480px) and (orientation: portrait) { :root { - --top-header-height: 140px; + --top-header-height: 160px; } #search-shortcut-key {