Skip to main content

everruns_openui/
lib.rs

1//! OpenUI component library and prompt generator for Everruns.
2//!
3//! Rust reimplementation of the OpenUI component definitions and prompt generator.
4//! Produces system prompts that instruct LLMs to generate OpenUI Lang code.
5//!
6//! The generated prompt is compatible with `@openuidev/react-lang` and `@openuidev/react-ui`
7//! — the React runtime parses the LLM output and renders it into interactive components.
8//!
9//! This crate is part of the [Everruns](https://everruns.com) ecosystem.
10//!
11//! # Example
12//!
13//! ```
14//! use everruns_openui::{PromptOptions, default_library, generate_prompt};
15//!
16//! let prompt = generate_prompt(default_library(), &PromptOptions::default());
17//! assert!(prompt.contains("```openui"));
18//! ```
19//!
20//! Ref: <https://github.com/thesysdev/openui>
21//! - Component definitions: packages/react-ui/src/genui-lib/
22//! - Prompt generator: packages/react-lang/src/parser/prompt.ts
23//! - Library/types: packages/react-lang/src/library.ts
24
25mod components;
26mod groups;
27mod prompt;
28
29pub use components::{ComponentDef, Library, PropDef};
30pub use groups::ComponentGroup;
31pub use prompt::{PromptOptions, generate_prompt};
32
33use std::sync::LazyLock;
34
35/// The default OpenUI library matching `@openuidev/react-ui` openuiChatLibrary.
36/// Ref: packages/react-ui/src/genui-lib/openuiChatLibrary.tsx
37pub fn default_library() -> &'static Library {
38    &DEFAULT_LIBRARY
39}
40
41static DEFAULT_LIBRARY: LazyLock<Library> = LazyLock::new(|| Library {
42    root: "Card",
43    components: components::all_components(),
44    groups: groups::default_groups(),
45});
46
47/// Generate the default OpenUI system prompt with standard options.
48///
49/// This is the convenience function used by the OpenUI capability.
50pub fn default_prompt() -> &'static str {
51    &DEFAULT_PROMPT
52}
53
54static DEFAULT_PROMPT: LazyLock<String> =
55    LazyLock::new(|| generate_prompt(default_library(), &PromptOptions::default()));
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn test_default_library_has_components() {
63        let lib = default_library();
64        assert!(!lib.components.is_empty());
65        assert_eq!(lib.root, "Card");
66    }
67
68    #[test]
69    fn test_default_library_has_all_expected_components() {
70        let lib = default_library();
71        let names: Vec<&str> = lib.components.iter().map(|c| c.name).collect();
72
73        // Layout
74        assert!(names.contains(&"Stack"));
75        assert!(names.contains(&"Tabs"));
76        assert!(names.contains(&"Accordion"));
77
78        // Content
79        assert!(names.contains(&"Card"));
80        assert!(names.contains(&"TextContent"));
81        assert!(names.contains(&"CodeBlock"));
82
83        // Charts
84        assert!(names.contains(&"BarChart"));
85        assert!(names.contains(&"PieChart"));
86        assert!(names.contains(&"LineChart"));
87
88        // Tables
89        assert!(names.contains(&"Table"));
90
91        // Forms
92        assert!(names.contains(&"Form"));
93        assert!(names.contains(&"Input"));
94        assert!(names.contains(&"Select"));
95
96        // Buttons
97        assert!(names.contains(&"Button"));
98
99        // Chat-specific
100        assert!(names.contains(&"ListBlock"));
101        assert!(names.contains(&"FollowUpBlock"));
102        assert!(names.contains(&"SectionBlock"));
103    }
104
105    #[test]
106    fn test_default_prompt_not_empty() {
107        let prompt = default_prompt();
108        assert!(!prompt.is_empty());
109        assert!(prompt.len() > 500);
110    }
111
112    #[test]
113    fn test_default_prompt_contains_key_sections() {
114        let prompt = default_prompt();
115        assert!(prompt.contains("## Syntax Rules"));
116        assert!(prompt.contains("## Component Signatures"));
117        assert!(prompt.contains("## Hoisting & Streaming"));
118        assert!(prompt.contains("## Important Rules"));
119    }
120
121    #[test]
122    fn test_default_prompt_instructs_openui_code_blocks() {
123        let prompt = default_prompt();
124        assert!(prompt.contains("```openui"));
125    }
126
127    #[test]
128    fn test_component_count() {
129        let lib = default_library();
130        // Should have all ~55 components from the chat library
131        assert!(
132            lib.components.len() >= 50,
133            "Expected at least 50 components, got {}",
134            lib.components.len()
135        );
136    }
137
138    #[test]
139    fn test_groups_cover_all_components() {
140        let lib = default_library();
141        let grouped: Vec<&str> = lib
142            .groups
143            .iter()
144            .flat_map(|g| g.components.iter().copied())
145            .collect();
146
147        for comp in &lib.components {
148            assert!(
149                grouped.contains(&comp.name),
150                "Component '{}' is not in any group",
151                comp.name
152            );
153        }
154    }
155
156    #[test]
157    fn test_prompt_contains_all_component_signatures() {
158        let lib = default_library();
159        let prompt = default_prompt();
160
161        for comp in &lib.components {
162            assert!(
163                prompt.contains(&format!("{}(", comp.name)),
164                "Prompt missing signature for component '{}'",
165                comp.name
166            );
167        }
168    }
169}