//! OpenUI Lang, the small language agents write ` ```openui ` blocks in: //! its parser and evaluator, kept free of GPUI so they can be tested on //! their own. `ui::inline` renders what they produce. pub mod eval; pub mod lang; /// What agents are told about rich blocks, when the setting is on: in the /// system prompt for Claude and Pi, and with jcode's first message. pub const INSTRUCTIONS: &str = r##"## Inline UI in Vectrum Code Vectrum Code shows your Markdown answers and renders some fenced code blocks live. Plain Markdown comes first; use these only when a picture, table or control says it better than prose: architecture and flows, comparisons, results data, choices for the user to make. - ```mermaid: a diagram (flowchart, sequenceDiagram, classDiagram, stateDiagram-v2, erDiagram, gantt, pie, mindmap). - ```svg: a small self-contained SVG image, with width and height set. - ```html: a self-contained page with inline CSS and JS. It shows as code; the user can open it with Preview. - ```openui: native UI written in OpenUI Lang, below. OpenUI Lang: one statement per line, `name = Expression`, `root = ...` first; forward references are fine. Arguments are positional, and optional ones can be left off the end. Values: strings, numbers, true/false/null, [arrays], {key: value} objects, references to other statements, `a.b` (on an array it takes `b` from every element), `cond ? a : b`, arithmetic, comparisons, && || !. State: declare `$name = default` and read `$name`; an input given `$name` as its first argument edits it, and everything reading it updates. Built-ins: @Count @Sum @Avg @Min @Max @First @Last @Round(n, decimals) @Abs @Floor @Ceil @Filter(array, field, op, value) @Sort(array, field, "asc"|"desc") @Each(array, "item", template). Actions, a Button's second argument: Action([steps]) with @Set($x, value), @Reset($x), @ToAssistant("message"), @OpenUrl("https://..."); or "submit:formName" to send a form's values to you, or "action:name". Components: - Layout: Stack(children, direction "column"|"row", gap "s"|"m"|"l", align, justify, wrap), Card(children, variant "raised"|"well"|"plain"), CardHeader(title, subtitle), Tabs([TabItem(value, label, content)]), Separator(). - Content: TextContent(text, size "small"|"default"|"large"|"large-heavy"), Markdown(text), Callout(variant "info"|"success"|"warning"|"error", title, description), Tag(text, variant), Stat(label, value, delta), Progress(value 0..1 or 0..100, label), Image(url, alt), CodeBlock(language, code). - Data: Table([Col(label, values, type "text"|"number"|"tag")]), BarChart(labels, [Series(name, values)], "grouped"|"stacked", xLabel, yLabel), LineChart and AreaChart(labels, [Series(name, values)], xLabel, yLabel), PieChart(labels, values). - Input: Form(name, [fields], Buttons([...])), FormControl(label, input, hint), Input(name, placeholder, type "text"|"email"|"number"|"password", rules ["required", "email"]), TextArea(name, placeholder, rows), Select(name, options, placeholder), Checkbox(name, label), Switch(name, label), Slider(name, min, max, step), Buttons([buttons], direction), Button(label, action, variant "primary"|"secondary"|"danger"|"ghost"). Query, Mutation and @Run aren't available. When the user acts on your UI you get a message starting with [UI], such as `[UI] Submitted "deploy": {"env":"staging"}`. Answer it in a new message; earlier blocks keep their state. Examples: ```openui root = Card([header, form]) header = CardHeader("Deploy", "Pick a target") form = Form("deploy", [FormControl("Environment", Select("env", ["staging", "production"]))], Buttons([Button("Deploy", "submit:deploy", "primary")])) ``` ```openui $count = 0 root = Stack([TextContent("Count: " + $count, "large"), Buttons([Button("-", Action([@Set($count, $count - 1)])), Button("+", Action([@Set($count, $count + 1)]), "primary")], "row")]) ```"##; #[cfg(test)] mod tests { use super::{INSTRUCTIONS, eval, lang}; /// The examples agents are shown must parse and render. #[::core::prelude::v1::test] fn instruction_examples_parse() { let blocks: Vec<&str> = INSTRUCTIONS.split("```openui\n").skip(1).map(|block| block.split("```").next().unwrap_or_default()).collect(); assert_eq!(blocks.len(), 2); for block in blocks { let program = lang::parse(block); assert!(program.errors.is_empty(), "{block}: {:?}", program.errors); assert!(program.undefined().is_empty(), "{block}"); assert!(!eval::evaluate(&program, &eval::State::new()).is_pending()); } assert!(INSTRUCTIONS.lines().count() < 60); } }