From a9f04c8fb13762ff83cd33dbca3d97711381855e Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Mon, 29 Sep 2025 11:36:28 +0800 Subject: [PATCH 1/6] add help text Signed-off-by: Teo Koon Peng --- .../diagram/calculator_ops_catalog/src/lib.rs | 24 ++++++++++++------- registry.schema.json | 6 +++++ src/diagram/registration.rs | 10 ++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/examples/diagram/calculator_ops_catalog/src/lib.rs b/examples/diagram/calculator_ops_catalog/src/lib.rs index 09409f0f..4389a74e 100644 --- a/examples/diagram/calculator_ops_catalog/src/lib.rs +++ b/examples/diagram/calculator_ops_catalog/src/lib.rs @@ -138,7 +138,9 @@ pub struct FibonacciStream { pub fn register(registry: &mut DiagramElementRegistry) { registry.register_node_builder( - NodeBuilderOptions::new("add").with_default_display_text("Add"), + NodeBuilderOptions::new("add") + .with_default_display_text("Add") + .with_help_text("Adds the input with the config value. The input can be a number or an array of numbers, if it is an array, sum all the items in the array and the config value."), |builder, config: Option| { builder.create_map_block(move |req: JsonMessage| { let input = match req { @@ -163,7 +165,7 @@ pub fn register(registry: &mut DiagramElementRegistry) { ); registry.register_node_builder( - NodeBuilderOptions::new("sub").with_default_display_text("Subtract"), + NodeBuilderOptions::new("sub").with_default_display_text("Subtract").with_help_text("Subtract the config value from the input. The input can be a number or an array of numbers, if it is an array, the first item is subtracted by the rest of the items and the config."), |builder, config: Option| { builder.create_map_block(move |req: JsonMessage| { let input = match req { @@ -188,7 +190,7 @@ pub fn register(registry: &mut DiagramElementRegistry) { ); registry.register_node_builder( - NodeBuilderOptions::new("mul").with_default_display_text("Multiply"), + NodeBuilderOptions::new("mul").with_default_display_text("Multiply").with_help_text("Multiply the input and the config value. The input can be a number or an array, if it is an array, multiply each item and the config value."), |builder, config: Option| { builder.create_map_block(move |req: JsonMessage| { let input = match req { @@ -213,7 +215,7 @@ pub fn register(registry: &mut DiagramElementRegistry) { ); registry.register_node_builder( - NodeBuilderOptions::new("div").with_default_display_text("Divide"), + NodeBuilderOptions::new("div").with_default_display_text("Divide").with_help_text("Divide the input by the config value. The input can be a number or an array, if it is an array, divide the first item by the rest of the item and the config."), |builder, config: Option| { builder.create_map_block(move |req: JsonMessage| { let input = match req { @@ -238,7 +240,9 @@ pub fn register(registry: &mut DiagramElementRegistry) { ); registry.register_node_builder( - NodeBuilderOptions::new("fibonacci").with_default_display_text("Fibonacci"), + NodeBuilderOptions::new("fibonacci") + .with_default_display_text("Fibonacci") + .with_help_text("Streams the fibonacci sequence of the input or config value length. If a config value is given, the input is ignored."), |builder, config: Option| { builder.create_map( move |input: AsyncMap| async move { @@ -271,7 +275,11 @@ pub fn register(registry: &mut DiagramElementRegistry) { .no_serializing() .no_deserializing() .register_node_builder( - NodeBuilderOptions::new("print").with_default_display_text("Print"), + NodeBuilderOptions::new("print") + .with_default_display_text("Print") + .with_help_text( + "Prints the input to stdout. An optional string can be provided in the config to label the output.", + ), |builder, config: Option| { let header = config.clone(); builder.create_map_block(move |request: JsonMessage| { @@ -290,7 +298,7 @@ pub fn register(registry: &mut DiagramElementRegistry) { registry .register_node_builder( - NodeBuilderOptions::new("less_than").with_default_display_text("Less Than"), + NodeBuilderOptions::new("less_than").with_default_display_text("Less Than").with_help_text("Checks if the input is less than the config value. The input can be a number or an array of numbers, if it is an array, checks that all items in the array is less than the config value. If a config value is not provided, check that all items in the array is less than the next item (check that they are in ascending order)."), |builder, config: ComparisonConfig| { let settings: ComparisonSettings = config.into(); builder.create_map_block(move |request: JsonMessage| { @@ -302,7 +310,7 @@ pub fn register(registry: &mut DiagramElementRegistry) { registry .register_node_builder( - NodeBuilderOptions::new("greater_than").with_default_display_text("Greater Than"), + NodeBuilderOptions::new("greater_than").with_default_display_text("Greater Than").with_help_text("Checks if the input is greater than the config value. The input can be a number or an array of numbers, if it is an array, checks that all items in the array is greater than the config value. If a config value is not provided, check that all items in the array is greater than the next item (check that they are in descending order)."), |builder, config: ComparisonConfig| { let settings: ComparisonSettings = config.into(); builder.create_map_block(move |request: JsonMessage| { diff --git a/registry.schema.json b/registry.schema.json index feb15aa4..dfcc7b07 100644 --- a/registry.schema.json +++ b/registry.schema.json @@ -122,6 +122,12 @@ "description": "If the user does not specify a default display text, the node ID will\n be used here.", "type": "string" }, + "help_text": { + "type": [ + "string", + "null" + ] + }, "request": { "type": "string" }, diff --git a/src/diagram/registration.rs b/src/diagram/registration.rs index 15e52f7a..18a4f4a0 100644 --- a/src/diagram/registration.rs +++ b/src/diagram/registration.rs @@ -58,6 +58,7 @@ pub struct NodeRegistration { pub(super) response: TypeInfo, pub(super) streams: HashMap, TypeInfo>, pub(super) config_schema: Schema, + pub(super) help_text: Option, /// Creates an instance of the registered node. #[serde(skip)] create_node_impl: CreateNodeFn, @@ -190,6 +191,7 @@ impl<'a, DeserializeImpl, SerializeImpl, Cloneable> serde_json::from_value(config).map_err(DiagramErrorCode::ConfigError)?; Ok(f(builder, config).into()) })), + help_text: None, }; self.registry.nodes.insert(options.id.clone(), registration); @@ -1518,6 +1520,8 @@ pub struct NodeBuilderOptions { /// If this is not specified, the id field will be used as the default /// display text. pub default_display_text: Option, + /// Optional text to describe the builder. + pub help_text: Option, } impl NodeBuilderOptions { @@ -1525,6 +1529,7 @@ impl NodeBuilderOptions { Self { id: id.into(), default_display_text: None, + help_text: None, } } @@ -1532,6 +1537,11 @@ impl NodeBuilderOptions { self.default_display_text = Some(text.into()); self } + + pub fn with_help_text(&mut self, text: impl Into) -> &mut Self { + self.help_text = Some(text.into()); + self + } } #[non_exhaustive] From 582e5908bc82c109739f46708e60f50263d58cc4 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Mon, 29 Sep 2025 13:58:51 +0800 Subject: [PATCH 2/6] add help_text for sections Signed-off-by: Teo Koon Peng --- registry.schema.json | 6 ++++++ src/diagram/registration.rs | 30 ++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/registry.schema.json b/registry.schema.json index dfcc7b07..d7882ee2 100644 --- a/registry.schema.json +++ b/registry.schema.json @@ -225,6 +225,12 @@ "default_display_text": { "type": "string" }, + "help_text": { + "type": [ + "string", + "null" + ] + }, "metadata": { "$ref": "#/$defs/SectionMetadata" } diff --git a/src/diagram/registration.rs b/src/diagram/registration.rs index 18a4f4a0..7f18b6f7 100644 --- a/src/diagram/registration.rs +++ b/src/diagram/registration.rs @@ -191,7 +191,7 @@ impl<'a, DeserializeImpl, SerializeImpl, Cloneable> serde_json::from_value(config).map_err(DiagramErrorCode::ConfigError)?; Ok(f(builder, config).into()) })), - help_text: None, + help_text: options.help_text, }; self.registry.nodes.insert(options.id.clone(), registration); @@ -599,6 +599,7 @@ pub struct SectionRegistration { pub(super) default_display_text: DisplayText, pub(super) metadata: SectionMetadata, pub(super) config_schema: Schema, + pub(super) help_text: Option, #[serde(skip)] create_section_impl: RefCell>, @@ -622,7 +623,7 @@ where { fn into_section_registration( self, - name: BuilderId, + options: &SectionBuilderOptions, schema_generator: &mut SchemaGenerator, ) -> SectionRegistration; } @@ -635,17 +636,22 @@ where { fn into_section_registration( mut self, - name: BuilderId, + options: &SectionBuilderOptions, schema_generator: &mut SchemaGenerator, ) -> SectionRegistration { SectionRegistration { - default_display_text: name, + default_display_text: options + .default_display_text + .as_ref() + .unwrap_or(&options.id) + .clone(), metadata: SectionT::metadata().clone(), config_schema: schema_generator.subschema_for::<()>(), create_section_impl: RefCell::new(Box::new(move |builder, config| { let section = self(builder, serde_json::from_value::(config).unwrap()); Box::new(section) })), + help_text: options.help_text.clone(), } } } @@ -1386,12 +1392,8 @@ impl DiagramElementRegistry { SectionBuilder: IntoSectionRegistration, SectionT: Section, { - let reg = section_builder.into_section_registration( - options - .default_display_text - .unwrap_or_else(|| options.id.clone()), - &mut self.messages.schema_generator, - ); + let reg = section_builder + .into_section_registration(&options, &mut self.messages.schema_generator); self.sections.insert(options.id, reg); SectionT::on_register(self); } @@ -1552,6 +1554,8 @@ pub struct SectionBuilderOptions { /// If this is not specified, the id field will be used as the default /// display text. pub default_display_text: Option, + /// Optional text to describe the builder. + pub help_text: Option, } impl SectionBuilderOptions { @@ -1559,6 +1563,7 @@ impl SectionBuilderOptions { Self { id: id.into(), default_display_text: None, + help_text: None, } } @@ -1566,6 +1571,11 @@ impl SectionBuilderOptions { self.default_display_text = Some(text.into()); self } + + pub fn with_help_text(&mut self, text: impl Into) -> &mut Self { + self.help_text = Some(text.into()); + self + } } #[cfg(test)] From 9b1833503e59d5c1cdf33b201db5ab34f8cc6eeb Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Mon, 29 Sep 2025 14:26:20 +0800 Subject: [PATCH 3/6] fix build Signed-off-by: Teo Koon Peng --- src/diagram/registration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diagram/registration.rs b/src/diagram/registration.rs index 7f18b6f7..7ac86d73 100644 --- a/src/diagram/registration.rs +++ b/src/diagram/registration.rs @@ -1540,7 +1540,7 @@ impl NodeBuilderOptions { self } - pub fn with_help_text(&mut self, text: impl Into) -> &mut Self { + pub fn with_help_text(mut self, text: impl Into) -> Self { self.help_text = Some(text.into()); self } @@ -1572,7 +1572,7 @@ impl SectionBuilderOptions { self } - pub fn with_help_text(&mut self, text: impl Into) -> &mut Self { + pub fn with_help_text(mut self, text: impl Into) -> Self { self.help_text = Some(text.into()); self } From d8431cdeb5c8c44caa7dba500b911b0a825a2018 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 5 Oct 2025 13:37:12 +0800 Subject: [PATCH 4/6] Provide example configs separately from the node description Signed-off-by: Michael X. Grey --- .../calculator/diagrams/split_and_join.json | 9 +- .../diagram/calculator_ops_catalog/src/lib.rs | 191 ++++++++++++++++-- registry.schema.json | 54 +++-- src/diagram/registration.rs | 82 ++++++-- 4 files changed, 297 insertions(+), 39 deletions(-) diff --git a/examples/diagram/calculator/diagrams/split_and_join.json b/examples/diagram/calculator/diagrams/split_and_join.json index 9028ccd2..87403f1c 100644 --- a/examples/diagram/calculator/diagrams/split_and_join.json +++ b/examples/diagram/calculator/diagrams/split_and_join.json @@ -8,14 +8,16 @@ "type": "node", "builder": "mul", "next": "a00aa305-9763-4602-b638-6cb190e6c452", - "config": 10 + "config": 100, + "display_text": "x100" }, "fee7f385-2a74-4a87-b8a7-87b8bd03fdf8": { "type": "node", "builder": "add", "next": { "builtin": "terminate" - } + }, + "display_text": "sum" }, "31fb7423-5300-447a-b259-49c5c79654e7": { "type": "join", @@ -29,7 +31,8 @@ "type": "node", "builder": "mul", "next": "305f46be-cf0d-42ac-bd28-26d63746abc3", - "config": 100 + "config": 10, + "display_text": "x10" }, "74764679-1f94-49f3-8080-259e57f78be9": { "type": "split", diff --git a/examples/diagram/calculator_ops_catalog/src/lib.rs b/examples/diagram/calculator_ops_catalog/src/lib.rs index 4389a74e..d7c1c3f4 100644 --- a/examples/diagram/calculator_ops_catalog/src/lib.rs +++ b/examples/diagram/calculator_ops_catalog/src/lib.rs @@ -1,9 +1,12 @@ use std::fmt::Write; -use bevy_impulse::{AsyncMap, DiagramElementRegistry, JsonMessage, NodeBuilderOptions, StreamPack}; +use bevy_impulse::{ + AsyncMap, ConfigExample, DiagramElementRegistry, JsonMessage, + NodeBuilderOptions, StreamPack, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_json::{Number, Value}; +use serde_json::{Number, Value, json}; #[derive(Clone, Copy, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -137,10 +140,25 @@ pub struct FibonacciStream { } pub fn register(registry: &mut DiagramElementRegistry) { + let add_description = "Add together any set of numbers passed as input and \ + then add the value in the config. If only one number is passed in as \ + input, it will be added to the value set in the config."; + let add_examples = [ + ConfigExample::new( + "Simply sum the set of numbers passed as input.", + json!{null}, + ), + ConfigExample::new( + "Sum the set of numbers passed as input, and then add 5.", + json!{5.0}, + ), + ]; + registry.register_node_builder( NodeBuilderOptions::new("add") .with_default_display_text("Add") - .with_help_text("Adds the input with the config value. The input can be a number or an array of numbers, if it is an array, sum all the items in the array and the config value."), + .with_description(add_description) + .with_examples_configs(add_examples), |builder, config: Option| { builder.create_map_block(move |req: JsonMessage| { let input = match req { @@ -164,8 +182,26 @@ pub fn register(registry: &mut DiagramElementRegistry) { }, ); + let sub_description = "Subtract some numbers. If an array of numbers is \ + passed as input then the first number will be subtracted by every \ + subsequent number. If a number is set in the config, that will also be \ + subtracted from the output."; + let sub_examples = [ + ConfigExample::new( + "Simply subtract the first array element by all subsequent elements.", + json!{null}, + ), + ConfigExample::new( + "Additionally subtract 5 from the output.", + json!{5.0}, + ), + ]; + registry.register_node_builder( - NodeBuilderOptions::new("sub").with_default_display_text("Subtract").with_help_text("Subtract the config value from the input. The input can be a number or an array of numbers, if it is an array, the first item is subtracted by the rest of the items and the config."), + NodeBuilderOptions::new("sub") + .with_default_display_text("Subtract") + .with_description(sub_description) + .with_examples_configs(sub_examples), |builder, config: Option| { builder.create_map_block(move |req: JsonMessage| { let input = match req { @@ -189,8 +225,26 @@ pub fn register(registry: &mut DiagramElementRegistry) { }, ); + let mul_description = "Multiply some numbers. If an array of numbers is \ + passed as input then all the numbers will be multiplied together. If \ + a number is set in the config, that will also be multipled into the \ + output."; + let mul_examples = [ + ConfigExample::new( + "Simply multiply the input numbers together.", + json!{null}, + ), + ConfigExample::new( + "Additionally multiple the output by 5.", + json!{5.0}, + ), + ]; + registry.register_node_builder( - NodeBuilderOptions::new("mul").with_default_display_text("Multiply").with_help_text("Multiply the input and the config value. The input can be a number or an array, if it is an array, multiply each item and the config value."), + NodeBuilderOptions::new("mul") + .with_default_display_text("Multiply") + .with_description(mul_description) + .with_examples_configs(mul_examples), |builder, config: Option| { builder.create_map_block(move |req: JsonMessage| { let input = match req { @@ -214,8 +268,26 @@ pub fn register(registry: &mut DiagramElementRegistry) { }, ); + let div_description = "Divide some numbers. If an array of numbers is \ + passed as input then the first number will be divided by all \ + subsequent numbers. If a number is set in the config, the final output \ + will also be divided by that value."; + let div_examples = [ + ConfigExample::new( + "Simply divide the first array element by all subsequent elements.", + json!{null}, + ), + ConfigExample::new( + "Additionally divide the output by 2.", + json!{2.0}, + ), + ]; + registry.register_node_builder( - NodeBuilderOptions::new("div").with_default_display_text("Divide").with_help_text("Divide the input by the config value. The input can be a number or an array, if it is an array, divide the first item by the rest of the item and the config."), + NodeBuilderOptions::new("div") + .with_default_display_text("Divide") + .with_description(div_description) + .with_examples_configs(div_examples), |builder, config: Option| { builder.create_map_block(move |req: JsonMessage| { let input = match req { @@ -239,10 +311,30 @@ pub fn register(registry: &mut DiagramElementRegistry) { }, ); + let fibonacci_description = "Stream out a Fibonacci sequence. If a number \ + is given in the config, that will be used as the order of the \ + sequence. If no config is given then the input value will be \ + interpreted as a number and used as the order of the sequence. If no \ + suitable number can be found for the order then this will return an Err + containing the input message."; + let fibonacci_examples = [ + ConfigExample::new( + "Generate a Fibonacci sequence whose order is the input value. If \ + the input message cannot be interpreted as a number then this node \ + will return an Err.", + json!{null}, + ), + ConfigExample::new( + "Generate a Fibonacci sequence of order 10.", + json!{10.0}, + ), + ]; + registry.register_node_builder( NodeBuilderOptions::new("fibonacci") .with_default_display_text("Fibonacci") - .with_help_text("Streams the fibonacci sequence of the input or config value length. If a config value is given, the input is ignored."), + .with_description(fibonacci_description) + .with_examples_configs(fibonacci_examples), |builder, config: Option| { builder.create_map( move |input: AsyncMap| async move { @@ -270,6 +362,20 @@ pub fn register(registry: &mut DiagramElementRegistry) { }, ); + let print_description = "Prints the input to stdout. An optional string \ + can be provided in the config to label the output."; + + let print_examples = [ + ConfigExample::new( + "Print the input as-is", + json!{null}, + ), + ConfigExample::new( + "Add \"printed from node: \" to the printed message", + json!{"printed from node"}, + ), + ]; + registry .opt_out() .no_serializing() @@ -277,9 +383,8 @@ pub fn register(registry: &mut DiagramElementRegistry) { .register_node_builder( NodeBuilderOptions::new("print") .with_default_display_text("Print") - .with_help_text( - "Prints the input to stdout. An optional string can be provided in the config to label the output.", - ), + .with_description(print_description) + .with_examples_configs(print_examples), |builder, config: Option| { let header = config.clone(); builder.create_map_block(move |request: JsonMessage| { @@ -296,9 +401,40 @@ pub fn register(registry: &mut DiagramElementRegistry) { ) .with_deserialize_request(); + let less_than_description = "Compares for a less-than relationship, \ + returning a Result based on the evaluation. Inputs can be an \ + array of numbers or a single number value. The exact behavior will \ + depend on the config (see examples)."; + + let less_than_examples = [ + ConfigExample::new( + "Verify that every element in the input array is less than the next one.", + ComparisonConfig::None, + ), + ConfigExample::new( + "Verify that every element in the input array is less than OR EQUAL to the next one.", + ComparisonConfig::OrEqual(OrEqualTag::OrEqual), + ), + ConfigExample::new( + "Verify that every element in the input array is less than 10.", + ComparisonConfig::ComparedTo(10.0), + ), + ConfigExample::new( + "Verify that every element in the input array is less than or \ + equal to 10.", + ComparisonConfig::Settings(ComparisonSettings { + compared_to: Some(10.0), + or_equal: true, + }), + ), + ]; + registry .register_node_builder( - NodeBuilderOptions::new("less_than").with_default_display_text("Less Than").with_help_text("Checks if the input is less than the config value. The input can be a number or an array of numbers, if it is an array, checks that all items in the array is less than the config value. If a config value is not provided, check that all items in the array is less than the next item (check that they are in ascending order)."), + NodeBuilderOptions::new("less_than") + .with_default_display_text("Less Than") + .with_description(less_than_description) + .with_examples_configs(less_than_examples), |builder, config: ComparisonConfig| { let settings: ComparisonSettings = config.into(); builder.create_map_block(move |request: JsonMessage| { @@ -308,9 +444,40 @@ pub fn register(registry: &mut DiagramElementRegistry) { ) .with_fork_result(); + let greater_than_description = "Compares for a greater-than relationship, \ + returning a Result based on the evaluation. Inputs can be an \ + array of numbers or a single number value. The exact behavior will \ + depend on the config (see examples)."; + + let greater_than_examples = [ + ConfigExample::new( + "Verify that every element in the input array is greater than the next one.", + ComparisonConfig::None, + ), + ConfigExample::new( + "Verify that every element in the input array is greater than OR EQUAL to the next one.", + ComparisonConfig::OrEqual(OrEqualTag::OrEqual), + ), + ConfigExample::new( + "Verify that every element in the input array is greater than 10.", + ComparisonConfig::ComparedTo(10.0), + ), + ConfigExample::new( + "Verify that every element in the input array is greater than or \ + equal to 10.", + ComparisonConfig::Settings(ComparisonSettings { + compared_to: Some(10.0), + or_equal: true, + }), + ), + ]; + registry .register_node_builder( - NodeBuilderOptions::new("greater_than").with_default_display_text("Greater Than").with_help_text("Checks if the input is greater than the config value. The input can be a number or an array of numbers, if it is an array, checks that all items in the array is greater than the config value. If a config value is not provided, check that all items in the array is greater than the next item (check that they are in descending order)."), + NodeBuilderOptions::new("greater_than") + .with_default_display_text("Greater Than") + .with_description(greater_than_description) + .with_examples_configs(greater_than_examples), |builder, config: ComparisonConfig| { let settings: ComparisonSettings = config.into(); builder.create_map_block(move |request: JsonMessage| { diff --git a/registry.schema.json b/registry.schema.json index d7882ee2..34a96bc5 100644 --- a/registry.schema.json +++ b/registry.schema.json @@ -37,6 +37,22 @@ "schemas" ], "$defs": { + "ConfigExample": { + "type": "object", + "properties": { + "description": { + "description": "A description of what this config is for", + "type": "string" + }, + "config": { + "description": "The value of the config" + } + }, + "required": [ + "description", + "config" + ] + }, "MessageOperation": { "type": "object", "properties": { @@ -115,6 +131,12 @@ "NodeRegistration": { "type": "object", "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, "config_schema": { "$ref": "#/$defs/Schema" }, @@ -122,11 +144,11 @@ "description": "If the user does not specify a default display text, the node ID will\n be used here.", "type": "string" }, - "help_text": { - "type": [ - "string", - "null" - ] + "example_configs": { + "type": "array", + "items": { + "$ref": "#/$defs/ConfigExample" + } }, "request": { "type": "string" @@ -146,7 +168,8 @@ "request", "response", "streams", - "config_schema" + "config_schema", + "example_configs" ] }, "Schema": { @@ -219,17 +242,23 @@ "SectionRegistration": { "type": "object", "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, "config_schema": { "$ref": "#/$defs/Schema" }, "default_display_text": { "type": "string" }, - "help_text": { - "type": [ - "string", - "null" - ] + "example_configs": { + "type": "array", + "items": { + "$ref": "#/$defs/ConfigExample" + } }, "metadata": { "$ref": "#/$defs/SectionMetadata" @@ -238,7 +267,8 @@ "required": [ "default_display_text", "metadata", - "config_schema" + "config_schema", + "example_configs" ] } } diff --git a/src/diagram/registration.rs b/src/diagram/registration.rs index c6248892..4f4da02b 100644 --- a/src/diagram/registration.rs +++ b/src/diagram/registration.rs @@ -61,7 +61,8 @@ pub struct NodeRegistration { pub(super) response: TypeInfo, pub(super) streams: HashMap, TypeInfo>, pub(super) config_schema: Schema, - pub(super) help_text: Option, + pub(super) description: Option, + pub(super) example_configs: Vec, /// Creates an instance of the registered node. #[serde(skip)] @@ -244,7 +245,8 @@ impl<'a, DeserializeImpl, SerializeImpl, Cloneable> Ok(node.into()) })), - help_text: options.help_text, + description: options.description, + example_configs: options.examples_configs, }; self.registry.nodes.insert(options.id.clone(), registration); @@ -652,7 +654,8 @@ pub struct SectionRegistration { pub(super) default_display_text: DisplayText, pub(super) metadata: SectionMetadata, pub(super) config_schema: Schema, - pub(super) help_text: Option, + pub(super) description: Option, + pub(super) example_configs: Vec, #[serde(skip)] create_section_impl: RefCell>, @@ -704,7 +707,8 @@ where let section = self(builder, serde_json::from_value::(config).unwrap()); Box::new(section) })), - help_text: options.help_text.clone(), + description: options.description.clone(), + example_configs: options.example_configs.clone(), } } } @@ -1591,6 +1595,7 @@ impl DiagramElementRegistry { } } +#[derive(Clone)] #[non_exhaustive] pub struct NodeBuilderOptions { /// The unique identifier for this node builder. Diagrams will use this ID @@ -1600,7 +1605,40 @@ pub struct NodeBuilderOptions { /// display text. pub default_display_text: Option, /// Optional text to describe the builder. - pub help_text: Option, + pub description: Option, + /// Examples of configurations that can be used with this node builder. + pub examples_configs: Vec, +} + +#[derive(Clone, Serialize, JsonSchema)] +pub struct ConfigExample { + /// A description of what this config is for + pub description: String, + /// The value of the config + pub config: JsonMessage, +} + +impl ConfigExample { + /// Create a new config example. + /// + /// Note that this function will panic if the `config` argument fails to be + /// serialized into a [`JsonMessage`], which happens if the data structure + /// contains a map with non-string keys or its [`Serialize`] implementation + /// produces an error. It's recommended to only use this during application + /// startup to avoid runtime failures. + /// + /// To construct a [`ConfigExample`] with no risk of panicking, you can + /// directly use normal structure initialization. + pub fn new( + description: impl ToString, + config: impl Serialize, + ) -> Self { + Self { + description: description.to_string(), + config: serde_json::to_value(config) + .expect("failed to serialize example config"), + } + } } impl NodeBuilderOptions { @@ -1608,7 +1646,8 @@ impl NodeBuilderOptions { Self { id: id.into(), default_display_text: None, - help_text: None, + description: None, + examples_configs: Default::default(), } } @@ -1617,8 +1656,16 @@ impl NodeBuilderOptions { self } - pub fn with_help_text(mut self, text: impl Into) -> Self { - self.help_text = Some(text.into()); + pub fn with_description(mut self, text: impl Into) -> Self { + self.description = Some(text.into()); + self + } + + pub fn with_examples_configs( + mut self, + example_configs: impl IntoIterator, + ) -> Self { + self.examples_configs = example_configs.into_iter().collect(); self } } @@ -1632,7 +1679,9 @@ pub struct SectionBuilderOptions { /// display text. pub default_display_text: Option, /// Optional text to describe the builder. - pub help_text: Option, + pub description: Option, + /// Examples of configurations that can be used with this section builder. + pub example_configs: Vec, } impl SectionBuilderOptions { @@ -1640,7 +1689,8 @@ impl SectionBuilderOptions { Self { id: id.into(), default_display_text: None, - help_text: None, + description: None, + example_configs: Default::default(), } } @@ -1649,8 +1699,16 @@ impl SectionBuilderOptions { self } - pub fn with_help_text(mut self, text: impl Into) -> Self { - self.help_text = Some(text.into()); + pub fn with_description(mut self, text: impl Into) -> Self { + self.description = Some(text.into()); + self + } + + pub fn with_example_configs( + mut self, + example_configs: impl IntoIterator, + ) -> Self { + self.example_configs = example_configs.into_iter().collect(); self } } From edc8bd9cb25a5754116fb91b33b39191e3244021 Mon Sep 17 00:00:00 2001 From: "Michael X. Grey" Date: Sun, 5 Oct 2025 13:43:34 +0800 Subject: [PATCH 5/6] Fix style Signed-off-by: Michael X. Grey --- .../diagram/calculator_ops_catalog/src/lib.rs | 47 ++++++------------- src/diagram/registration.rs | 8 +--- 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/examples/diagram/calculator_ops_catalog/src/lib.rs b/examples/diagram/calculator_ops_catalog/src/lib.rs index d7c1c3f4..f128f9de 100644 --- a/examples/diagram/calculator_ops_catalog/src/lib.rs +++ b/examples/diagram/calculator_ops_catalog/src/lib.rs @@ -1,12 +1,11 @@ use std::fmt::Write; use bevy_impulse::{ - AsyncMap, ConfigExample, DiagramElementRegistry, JsonMessage, - NodeBuilderOptions, StreamPack, + AsyncMap, ConfigExample, DiagramElementRegistry, JsonMessage, NodeBuilderOptions, StreamPack, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_json::{Number, Value, json}; +use serde_json::{json, Number, Value}; #[derive(Clone, Copy, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -146,11 +145,11 @@ pub fn register(registry: &mut DiagramElementRegistry) { let add_examples = [ ConfigExample::new( "Simply sum the set of numbers passed as input.", - json!{null}, + json!(null), ), ConfigExample::new( "Sum the set of numbers passed as input, and then add 5.", - json!{5.0}, + json!(5.0), ), ]; @@ -189,12 +188,9 @@ pub fn register(registry: &mut DiagramElementRegistry) { let sub_examples = [ ConfigExample::new( "Simply subtract the first array element by all subsequent elements.", - json!{null}, - ), - ConfigExample::new( - "Additionally subtract 5 from the output.", - json!{5.0}, + json!(null), ), + ConfigExample::new("Additionally subtract 5 from the output.", json!(5.0)), ]; registry.register_node_builder( @@ -230,14 +226,8 @@ pub fn register(registry: &mut DiagramElementRegistry) { a number is set in the config, that will also be multipled into the \ output."; let mul_examples = [ - ConfigExample::new( - "Simply multiply the input numbers together.", - json!{null}, - ), - ConfigExample::new( - "Additionally multiple the output by 5.", - json!{5.0}, - ), + ConfigExample::new("Simply multiply the input numbers together.", json!(null)), + ConfigExample::new("Additionally multiple the output by 5.", json!(5.0)), ]; registry.register_node_builder( @@ -275,12 +265,9 @@ pub fn register(registry: &mut DiagramElementRegistry) { let div_examples = [ ConfigExample::new( "Simply divide the first array element by all subsequent elements.", - json!{null}, - ), - ConfigExample::new( - "Additionally divide the output by 2.", - json!{2.0}, + json!(null), ), + ConfigExample::new("Additionally divide the output by 2.", json!(2.0)), ]; registry.register_node_builder( @@ -322,12 +309,9 @@ pub fn register(registry: &mut DiagramElementRegistry) { "Generate a Fibonacci sequence whose order is the input value. If \ the input message cannot be interpreted as a number then this node \ will return an Err.", - json!{null}, - ), - ConfigExample::new( - "Generate a Fibonacci sequence of order 10.", - json!{10.0}, + json!(null), ), + ConfigExample::new("Generate a Fibonacci sequence of order 10.", json!(10.0)), ]; registry.register_node_builder( @@ -366,13 +350,10 @@ pub fn register(registry: &mut DiagramElementRegistry) { can be provided in the config to label the output."; let print_examples = [ - ConfigExample::new( - "Print the input as-is", - json!{null}, - ), + ConfigExample::new("Print the input as-is", json!(null)), ConfigExample::new( "Add \"printed from node: \" to the printed message", - json!{"printed from node"}, + json!("printed from node"), ), ]; diff --git a/src/diagram/registration.rs b/src/diagram/registration.rs index 4f4da02b..8fe58501 100644 --- a/src/diagram/registration.rs +++ b/src/diagram/registration.rs @@ -1629,14 +1629,10 @@ impl ConfigExample { /// /// To construct a [`ConfigExample`] with no risk of panicking, you can /// directly use normal structure initialization. - pub fn new( - description: impl ToString, - config: impl Serialize, - ) -> Self { + pub fn new(description: impl ToString, config: impl Serialize) -> Self { Self { description: description.to_string(), - config: serde_json::to_value(config) - .expect("failed to serialize example config"), + config: serde_json::to_value(config).expect("failed to serialize example config"), } } } From 4fcfcffa56b5e83009400dc4abb3747a2cebe7a0 Mon Sep 17 00:00:00 2001 From: Teo Koon Peng Date: Thu, 9 Oct 2025 15:21:04 +0800 Subject: [PATCH 6/6] add description tooltip Signed-off-by: Teo Koon Peng --- .../frontend/api.preprocessed.schema.json | 46 ++++++++++++++++++- diagram-editor/frontend/forms/node-form.tsx | 29 +++++++++++- diagram-editor/frontend/nodes/icons.tsx | 7 ++- diagram-editor/frontend/types/api.d.ts | 21 +++++++++ 4 files changed, 99 insertions(+), 4 deletions(-) diff --git a/diagram-editor/frontend/api.preprocessed.schema.json b/diagram-editor/frontend/api.preprocessed.schema.json index d600e125..d899484f 100644 --- a/diagram-editor/frontend/api.preprocessed.schema.json +++ b/diagram-editor/frontend/api.preprocessed.schema.json @@ -114,6 +114,22 @@ } ] }, + "ConfigExample": { + "properties": { + "config": { + "description": "The value of the config" + }, + "description": { + "description": "A description of what this config is for", + "type": "string" + } + }, + "required": [ + "description", + "config" + ], + "type": "object" + }, "DebugSessionMessage": { "oneOf": [ { @@ -780,6 +796,18 @@ "description": "If the user does not specify a default display text, the node ID will\n be used here.", "type": "string" }, + "description": { + "type": [ + "string", + "null" + ] + }, + "example_configs": { + "items": { + "$ref": "#/$defs/ConfigExample" + }, + "type": "array" + }, "request": { "type": "string" }, @@ -798,7 +826,8 @@ "request", "response", "streams", - "config_schema" + "config_schema", + "example_configs" ], "type": "object" }, @@ -1027,6 +1056,18 @@ "default_display_text": { "type": "string" }, + "description": { + "type": [ + "string", + "null" + ] + }, + "example_configs": { + "items": { + "$ref": "#/$defs/ConfigExample" + }, + "type": "array" + }, "metadata": { "$ref": "#/$defs/SectionMetadata" } @@ -1034,7 +1075,8 @@ "required": [ "default_display_text", "metadata", - "config_schema" + "config_schema", + "example_configs" ], "type": "object" }, diff --git a/diagram-editor/frontend/forms/node-form.tsx b/diagram-editor/frontend/forms/node-form.tsx index a7e94920..d4411e81 100644 --- a/diagram-editor/frontend/forms/node-form.tsx +++ b/diagram-editor/frontend/forms/node-form.tsx @@ -1,5 +1,13 @@ -import { Autocomplete, TextField } from '@mui/material'; +import { + Autocomplete, + Box, + ListItem, + Stack, + TextField, + Tooltip, +} from '@mui/material'; import { useMemo, useState } from 'react'; +import { MaterialSymbol } from '../nodes'; import { useRegistry } from '../registry-provider'; import BaseEditOperationForm, { type BaseEditOperationFormProps, @@ -60,6 +68,25 @@ function NodeForm(props: NodeFormProps) { renderInput={(params) => ( )} + renderOption={({ key, ...otherProps }, value) => { + const nodeMetadata = registry.nodes[value]; + return ( + + + {value} + {nodeMetadata?.description && ( + + + + )} + + + ); + }} /> + {symbol} ); diff --git a/diagram-editor/frontend/types/api.d.ts b/diagram-editor/frontend/types/api.d.ts index 1c276874..a95a8887 100644 --- a/diagram-editor/frontend/types/api.d.ts +++ b/diagram-editor/frontend/types/api.d.ts @@ -354,6 +354,23 @@ export interface BufferSettings { retention: RetentionPolicy; [k: string]: unknown; } +/** + * This interface was referenced by `DiagramEditorApi`'s JSON-Schema + * via the `definition` "ConfigExample". + */ +export interface ConfigExample { + /** + * The value of the config + */ + config: { + [k: string]: unknown; + }; + /** + * A description of what this config is for + */ + description: string; + [k: string]: unknown; +} /** * This interface was referenced by `DiagramEditorApi`'s JSON-Schema * via the `definition` "Diagram". @@ -1114,6 +1131,8 @@ export interface NodeRegistration { * be used here. */ default_display_text: string; + description?: string | null; + example_configs: ConfigExample[]; request: string; response: string; streams: { @@ -1128,6 +1147,8 @@ export interface NodeRegistration { export interface SectionRegistration { config_schema: Schema; default_display_text: string; + description?: string | null; + example_configs: ConfigExample[]; metadata: SectionMetadata; [k: string]: unknown; }