Oracle by Example brandingCreate Different Response Types with the System.CommonResponse Component

section 0Before You Begin

This 45-minute tutorial shows you how to create different types of responses using just the System.CommonResponse component.

Note: Starting in Release 22.04, the Visual Flow Designer is available, which has a number of advantages over the YAML-based editor, including the ability to create modular flows and to design with much less code. If you are designing a new skill, you should use the Visual Flow Designer. See the Create a Dialog Flow with the Oracle Visual Flow Designer tutorial.

Background

The System.CommonResponse component is your multi-use tool for building UI components. As you progress through this tutorial, you'll see how this one component lets you build simple text prompts and list of values as well as rich UI like scrolling cards.

What Do You Need?

  • Access to an Oracle Digital Assistant instance, Version 19.1.3 or higher.
  • The CRCPizzaBot_Materials.zip file, which includes the starter skill and a finished version of the skill for your reference. Click here to download this file and then unzip it to your local system.

section 1Create the Skill

Import the Starter Skill

  1. Log into Oracle Digital Assistant.
  2. Click menu icon in the top left corner to open the side menu.
  3. Expand Development and then click Skills.
  4. Hide the menu by clicking menu icon again.
  5. Locate the CRCPizzaBot starter skill.
  6. If you can't locate the skill, click Import Skill (located at the upper right).
    An image of Skills dialog box with the Import Skill button highlighted.
    Description of the illustration.
  7. Browse to, and then select, CRCStarterSkill(1.0).zip. Then click Open.

Explore and Test the Skill

Take a look at the intents and entities.

  1. In the dashboard, click the CRCStarterSkill tile to open skill in the Skill Builder. The intent editor should be selected when this development environment is launched. If it isn't, click Intents Intent icon in the left navbar.
  2. Click Entities Entities Menu Icon in the left navbar. Notice the CheeseType, NoToppings, PizzaCrust, PizzaSize, PizzaToppings, and PizzaType entities that contribute the information required for a pizza order. Within the scope of this tutorial, you are just going to work with the PizzaType and PizzaSize entities.
  3. Click Intents Intent icon in the left navbar. Take a look at the two intents created for this skill: Welcome and OrderPizza. Notice that OrderPizza is associated with both the PizzaType and PizzaSize entities.
  4. Click Train (located on the right side of the top menu).
  5. Accept the default selections in the Train dialog and then click Submit.
  6. With the model now built, test it by clicking Try It Out! (located at the upper right).
  7. Enter I want to order a large pepperoni pizza in the Message field and then click Enter. Note that the returned confidence level for the OrderPizza intent (it's 100%). This time, however, only one of the two entities could be extracted from the user message, meaning that the skill must explicitly ask for the pizza size.
  8. Close the tester.
  9. Click Flows The dialog flow editor icon in the left navbar. Note the existing dialog flow states to handle the user intents and unresolved intents.

What you just did: You've imported the basic skill into your instance, trained it, and tested its intent resolution.



section 2Add Text Prompt Responses

In this section, you'll enable the skill to collect the user input for pizza type and pizza size using a text prompt. You will create these prompts by configuring the dialog flow with the  System.CommonResponse component. To print the user choice, you create a confirmation dialog flow state that uses the System.Output component.

  1. Click Flows The dialog flow editor icon in the left navbar.
  2. Click + Components (located at the top left) and then choose User Interface.
  3. Choose Common response - text.
  4. Choose unresolved in the Insert After menu.
  5. Switch on the Remove Comments toggle and then click Apply.
  6. Change the name of the added dialog flow state from textResponse to orderPizza.
  7. Define the following metadata properties:
    Property Value
    variable "pizzaType"
    nlpResultVariable "iResult"
    text "What kind of pizza do you want?"
  8. Delete everything between text and transitions.
  9. For the transitions property, replace
          actions: 
            someAction:
            
    with
        transitions:
          next: "askSize"
    (You'll add the askSize state in the next section.)
  10. Delete the unneeded properties, so that the orderPizza state looks like this:
      orderPizza:
        component: "System.CommonResponse"
        properties:
          processUserMessage: true
          keepTurn: false
          variable: "pizzaType"
          nlpResultVariable: "iResult"     
          maxPrompts: 
          autoNumberPostbackActions:
          translate:
          metadata: 
            responseItems:         
            - type: "text"  
              text: "What pizza do you want?"  
        transitions: 
          next: "askSize"

Click here to find this snippet.

Create the askSize State

  1. In the dialog flow, click + Components and then choose User Interface.
  2. Choose Common response - text.
  3. Choose orderPizza in the Insert After menu.
  4. Switch on the Remove Comments toggle and then click Apply.
  5. Change the name of the added dialog flow state from textResponse to askSize.
  6. Update the following properties:
    Property Value
    variable "pizzaSize"
    nlpResultvariable "iResult"
    text "What size pizza?"
  7. Delete everything between text and transitions.
  8. For the transitions property, replace
          actions: 
            someAction:
            
    with
        transitions:
          next: "confirmOrder" 
    (You'll add the confirmOrder state in the next section.)
  9. Delete any unneeded properties so that the askSize state looks like this:
    askSize:
        component: "System.CommonResponse"
        properties:
          processUserMessage: true
          keepTurn: false
          variable: "pizzaSize"
          nlpResultVariable: "iResult"
          metadata: 
            responseItems:         
            - type: "text"  
              text: "What size pizza?"  
        transitions:
          next: "confirmOrder" 

    Click here to find this snippet.

Create the confirmOrder State

  1. In the dialog flow, click + Components (located at the top left) and then choose User Interface.
  2. Click Output.
  3.  Choose askSize in the Insert After menu, switch on Remove Comments, then click Apply.
  4. Rename the newly added output state confirmOrder.
  5. Add the following Apache FreeMarker expression to the text property:
    "Your ${pizzaSize.value?lower_case} ${pizzaType.value?capitalize} pizza is on its way"
  6. Add the following transitions property:
       transitions:
          return: "done"

    When you're done, the confirmOrder state should look like this:

      confirmOrder:
        component: "System.Output"
        properties:
          text: "Your order of a ${pizzaSize.value?lower_case} ${pizzaType.value?capitalize} pizza is on its way"
          keepTurn: false
          translate:
        transitions:
          return: "done"
  7. Click Validate. If there are syntax errors that you can't resolve, replace the existing dialog flow definition with this one.

Test the Text Prompts

  1. Click Skill tester icon (located at the top right) to open the Skill Tester.
  2. Description of this image follows
    Description of the illustration follows.
  3. Enter I want to order a pizza.
  4. When prompted for the type, enter Pepperoni.
  5. When prompted for the size, enter large. The skill replies with Your large Pepperoni pizza is on its way.
  6. Click Reset to clear your input and then click Close The Close icon.
  7. The Reset button

What you just did: You defined the following properties while creating these messages:

  • variable--The value defined for this property references the dialog flow variable that is updated with the user input
  • nlpResultVariable--References a dialog flow variable that's of type natural language processing (NLP) result (iResult in this tutorial). The variable reference is used to suppress component rendering if the pizzaType information could be extracted from NLP.
  • processUserMessage--If set to true, then the System.CommonResponse component displays as an input component. If set to false, the component behaves like an output component.

section 3Add Select List Responses

Though your pizza skill works, the user experience needs some improvement. So, instead of outputting prompts for the pizza size and type, you're going enable the skill to output a list of values instead.

To build a list of values with the System.CommonResponse component, you can start from scratch using the dialog flow builder component templates, or modify the existing text prompt. In the following section, you're doing both. You'll create the orderPizza dialog flow using the component template and you'll extend the askSize state with a list.

Add a List of Values for the orderPizza State

  1. In the dialog flow, click + Components.
  2. Choose Common response - text, choose orderPizza in the Insert After menu.
  3. Choose Remove Comments and then click Apply.
  4. Delete the orderPizza state that you previously created so that the textResponse state is directly under the # TUTORIAL START comment:
    ...
    
    # TUTORIAL START
    
      textResponse:
        component: "System.CommonResponse"
        properties:
          # set processUserMessage to true if the dialog flow should return to this state after receiving user message
          processUserMessage: true
          # set keepTurn (true/false) to true if the dialog flow should transition to the next state without waiting for user input. Only applicable when processUserMessage is false
    
    ... 
                        
  5. Rename the textResponse state name to orderPizza.
  6. Set the variable property to "pizzaType".
  7. Set the nlpResultVariable property to "iResult".
  8. The responseItems element contains two text responses: the first is the Simple text sample response and the second (which is the one you're going to define), is the Text with actions sample. Remove the Simple text sample response by deleting the following block of code.
            - type: "text"  
              text: "Simple text sample"  
              footerText:
              iteratorVariable: 
              separateBubbles: false
              visible: 
                expression:            
                channels:
                  include:
                  exclude:  
                onInvalidUserInput:  
  9. Edit the remaining text response by adding the following properties:
    Property Value
    text
    "Which pizza do you want?"
    -label
    "${enumValue?capitalize}"
    iteratorVariable
    "pizzaType.type.enumValues"
  10. For the payload property, replace this:
                  action: "someAction"  
                  variables: 
                    user.someVariable: "someValue" 
    with this
                  variables: 
                    pizzaType: "${enumValue}"
  11. Edit the transition to navigate to the askSize state:
        transitions: 
          next: "askSize"
  12. Delete the unneeded properties so that the orderPizza looks like this:
      orderPizza:
        component: "System.CommonResponse"
        properties:
          processUserMessage: true
          keepTurn: false
          variable: "pizzaType" 
          nlpResultVariable: "iResult"       
          maxPrompts: 
    
          metadata: 
            responseItems:      
            - type: "text"  
              text: "What kind of pizze do you want?" 
              actions: 
              - label: "${enumValue?capitalize}"
                type: "postback"
                keyword: 
                payload: 
                  variables: 
                    pizzaType: "${enumValue}"
                iteratorVariable: "pizzaType.type.enumValues"   
        transitions: 
          next: "askSize" 
  13. Click Validate. If you're encountering syntax or spacing errors, paste in this snippet.

What you just did: You defined the following properties to create the content for these messages:

  • iteratorVariable--List items can be created (stamped) from an iterator. The PizzaType entity is of type value list, which means that it has a list of values defined that can be accessed through the pizzaType dialog flow variable.
  • label--When the list data is read from a value list entity, then the currently stamped item is accessed through the ${enumValue} expression. An Apache FreeMarker expression ?capitalize is used to create a user-friendly message format form the all-uppercase pizza type names in the entity.
  • variables--List item actions (postback actions) can update one or many variables when a user taps on an item in the list. In the pizza skill, there is only one dialog flow variable pizzaType that needs to be updated. The string pizzaType: "${enumValue}" assigns the current iterator value to the variable. So if, for example, a user taps on the Pepperoni list item, the PEPPERONI value is saved to the dialog flow variable.

Test the List

  1. Open the Skill Tester.
  2. Enter I want to order a pizza.
  3. Choose a pizza from the list.
  4. When prompted, for a size, enter large.
  5. Check the the confirmation messages outputs the correct variable values.
  6. Click Reset and then click then Close The Close icon to dismiss the Tester.
The select list in the Skill Tester
Description of the illustration.

Add a List of Values for the askSize State

Right now, the askSize state renders as an input prompt. In this section, you'll extend this prompt to be a list of values.

  1. If it's not still open, click Flows The dialog flow editor icon in the left navbar.
  2. Update the metadata property of the askSize state with the following code:
          metadata: 
            responseItems:         
            - type: "text"  
              text: "What size pizza?"
              actions:
              - label: "${enumValue?capitalize}"
                type: "postback"
                keyword:
                payload:
                  variables:              
                    pizzaSize: "${enumValue}"                
                iteratorVariable: "pizzaSize.type.enumValues"    
    When you're done, the state should look like this.
      askSize:
        component: "System.CommonResponse"
        properties:
          processUserMessage: true
          keepTurn: false
          variable: "pizzaSize"
          nlpResultVariable: "iResult"
          maxPrompts:
          metadata: 
            responseItems:         
            - type: "text"  
              text: "What size pizza?"
              actions:
              - label: "${enumValue?capitalize}"
                type: "postback"
                keyword:
                payload:
                  variables:              
                    pizzaSize: "${enumValue}"                
                iteratorVariable: "pizzaSize.type.enumValues"           
        transitions:
          next: "confirmOrder" 
  3. Click Validate.

What you just did: You extended the System.CommonResponse component with a list of values that are defined in these actions. Because the PizzaSize entity is a value list entity, its values can be read from the pizzaSize context variable (pizzaSize.type.enumValues).

You added Apache FreeMarker expressions with "?lower_case" and "?capitalize". Apache FreeMarker is an open source template language that can be used in Oracle Digital Assistant to format text messages. In this pizza example, the message values for pizza type and size are read from the entity. The pizza type value is defined all upper case in the entity, while the pizza size is defined with a capitalized first letter.

Without these expressions, the casing would be incorrect, but through Apache FreeMarker, the entity values are formatted such that the pizza type starts with an uppercase letter, followed by all lowercase letters, and the pizza size is printed in all lowercase.

Note: You could also have used the System.CommonResponse component in the confirmOrder state to confirm the pizza order. However, given that you only need to print a text message as a confirmation, using the System.Output component is the best choice. Only because the System.CommonResponse component can build any user interface, it does not mean it has to be used.

Test the List of Values

  1. Click Skill tester icon to open the Skill Tester.
  2. Enter I want to order a pizza.
  3. Select a pizza size, like Meat Lover.
  4. When prompted, select a size. The skill then outputs a confirmation message with the pizza type and size.
  5. Click Reset.
  6. Enter I want to order pizza with pepperoni.

    The skill then prompts you for the pizza size instead of the pizza type because the pizza type information was extracted from the message using NLP. This  works because of the nlpResultVariable property, which stores the property in iResult.

  7. Instead of entering the pizza size, enter Hmm, I want a large pizza. The skill outputs a confirmation message like Your order of a large Pepperoni pizza is on its way.
    The list of values.
    Description of the illustration.

    The variable property references the PizzaSize context variable, so any value that you provide when prompted for a pizza size is validated against the entity. The text message Hmm, I like a large pizza contains a valid size (large), so the value gets extracted.

  8. Click Reset.
  9. Enter I want to order a pizza.
  10. When the skill prompts for a pizza, enter salad instead.

    Again, the value is validated against the list of pizzas in the entity. Because salad is not a valid entity value, the skill displays the list of pizzas again.

  11. Click Reset and then close the Tester.

Add Keywords to the List Items

The orderPizza and askSize states both use stamped actions to render the list items. The action definitions include a keyword property, which is currently undefined. Keywords let you define shortcuts that users enter to select an action (a list item). You can define a single keyword or a comma-separated sequence of keywords. For example, l,L,grand,max are keyword shortcuts for a large pizza.

In this tutorial, the value list is dynamically created from the values provide by the PizzaType and PizzaSize list value entities. Because these values are created dynamically, the keywords also need to be created dynamically as well.

  1. Navigate to the askSize state.
  2. Add the following string to the keyword property:
    "${enumValue[0]?upper_case},${(enumValue?index)+1}"
                               
  3. Change the label property to:
    "(${enumValue[0]?upper_case})${enumValue?keep_after(enumValue[0])}"
                          

    When your done, the askSize state should look like this:

      askSize:
        component: "System.CommonResponse"
        properties:
          processUserMessage: true
          keepTurn: false
          variable: "pizzaSize"
          nlpResultVariable: "iResult"       
          metadata: 
            responseItems:         
            - type: "text"  
              text: "What size of pizza do you want?" 
              actions: 
              - label: "(${enumValue[0]?upper_case})${enumValue?keep_after(enumValue[0])}"
                type: "postback"
                keyword: "${enumValue[0]?upper_case},${(enumValue?index)+1}"
                payload: 
                  variables: 
                    pizzaSize: "${enumValue}" 
                iteratorVariable:  "pizzaSize.type.enumValues" 
        transitions: 
          next: "confirmOrder" 
    
  4. Click Validate. If you're encountering syntax errors that you can't fix, replace askSize with this snippet

What you just did: You added an Apache FreeMarker expression to define keywords as the first letter of the pizza size (L, or l for large, for example), as well as a numeric value, which matches the position of the item in the list. For example, you can enter 1 for large, 2 for medium.

Test the Keywords

  1. Click Skill tester icon to open the Skill Tester.
  2. Enter I want to order a pizza.
  3. Select a pizza type, like Meat Lover.
  4. When you're prompted for a pizza, note the label display.
  5. Instead of selecting one of the options, enter L for large. The skill outputs a confirmation for a large pizza.
  6. Click Reset.
  7. Enter I want to order a pizza.
  8. Select a pizza type.
  9. When the skill prompts you for a size, enter 2. The skill outputs a confirmation message for a medium pizza.
  10. Click Reset and then close the Skill Tester.
    The askSize select list.
    Description of the illustration.

Extra Credit

You added keyword support for the pizza size, so now you can do the same for the pizza type. Here's the challenge: Premium and Pepperoni both begin with the same letter, so you need to differentiate between the two by creating two-letter keyword shortcuts. If you need help, click here.


section 4Add Card Responses

The Oracle Digital Assistant Client SDK for JavaScript and Messaging platforms like Facebook support rich user interface components like cards. These messengers and they can display cards in a carousel that scrolls vertically or horizontally. In this section, you're going to update the orderPizza state to render the pizza types as a carousel of cards, with each card displaying an image. 

  1. Click Flows The dialog flow editor icon in the left navbar.
  2. Open the pizza_menu.txt file and paste its contents under the states node in the dialog flow.
  3. Click Validate.
  4. Delete the orderPizza state.
  5. Click + Components.
  6. Click User Interface then choose Common response - card.
  7. Choose unresolved from the Insert After menu.
  8. Rename the cardResponse state as orderPizza.
  9. In the properties node, add the following after processUserMessage: true:
    Property Value
    variable "pizzaType"
    nlpResultVariable "iResult"

    When you're done, the propreties node should look like this:

      orderPizza:
        component: "System.CommonResponse"
        properties:
          processUserMessage: true
          variable: "pizzaType"
          nlpResultVariable: "iResult"
          autoNumberPostbackActions:
          translate:
  10. Update the metadata property of the  orderPizza state:
    Property Value
    cardLayout "horizontal"
    title "${enumValue}"
    description "${pizzaCardInfo.value[enumValue].description}"
    imageUrl "${pizzaCardInfo.value[enumValue].image}"
    iteratorVariable "pizzaType.type.enumValues"
  11. In the actions property, update the label property as follows:
    - label: "Order ${enumValue?capitalize}"
  12. Replace
                  payload: 
                    action: "someAction"  
                    variables: 
                      user.someVariable: "someValue" 
    with
                  payload: 
                    action: "selectPizza"  
                    variables: 
                      pizzaType: "${enumValue}" 
  13. Replace the following code
                    phoneNumber:
                    url:
                  iteratorVariable: 
                  visible: 
                    expression:            
                    channels:
                      include:
                      exclude:  
                  skipAutoNumber:
              actions: []
            globalActions: []
    with
        transitions:
          next: "askSize"
          actions:
            selectPizza: "askSize"
  14. Delete the unneeded properties so that orderPizza state looks like this:
      orderPizza:
        component: "System.CommonResponse"
        properties:
          processUserMessage: true
          variable: "pizzaType"
          nlpResultVariable: "iResult"
          autoNumberPostbackActions:
          translate:
          metadata: 
            responseItems:         
            - type: "cards" 
              cardLayout: "horizontal"
              cards:
              - title: "${enumValue}"
                description: "${pizzaCardInfo.value[enumValue].description}"
                imageUrl: "${pizzaCardInfo.value[enumValue].image}"
                iteratorVariable: "pizzaType.type.enumValues"
                rangeStart:
                rangeSize:
                actions:
                - label: "Order ${enumValue?capitalize}"
                  type: "postback"
                  keyword:  
                  payload: 
                    action: "selectPizza"  
                    variables: 
                      pizzaType: "${enumValue}" 
        transitions:
          next: "askSize"
          actions:
            selectPizza: "askSize"
  15. Click Validate. If you're encountering syntax errors that you can't resolve, paste in this snippet.

What you just did:

In this section, you changed the skill response rendered by the System.CommonResponse component from a list type to a cards layout.

The LoadPizzaCardInfo state you copied from the pizza_menu.txt file uses Apache FreeMarker expressions to build a pizza menu object. In a production system, this object would be queried from a remote service using a custom component and then saved into a dialog flow variable.

As before, the card layout uses an iterator (the enum values of the PizzaType entity) to print the individual cards. The pizza names are used as a key name in the pizza menu object (pizzaCardInfo) to retrieve the image URL and description.

The actions element defines the buttons that are placed on a card. In the tutorial, each card is rendered with a single button for ordering a pizza. Tapping the button updates the pizzaType variable with the name of the pizza. At the same time, the selectPizza custom action is triggered. The selectPizza custom action is mapped to the askSize state, so navigation continues to asking for the pizza size.

Why is the next transition set to the same dialog flow state as the selectPizza transition? The selectPizza action transition is followed when a user clicks the button on a card. However, if the initial user message contained the name of the pizza to order, in which case natural language processing extracts it, then the next transition is followed. Selecting a button action may result in a different dialog flow state to be visited, which is why it is good practice to define a separate action transition for it.

Test the Cards

  1. Click Skill tester icon to open the Skill Tester. If needed, click Reset to clear the prior conversation.
  2. Enter I want to order a pizza.
  3. Scroll to the second pizza in the carousel (Pepperoni).
  4. Click Order.
  5. Select a pizza size.
  6. Click Reset.
  7. Enter I want to order a pepperoni pizza.

    In this conversation, you bypass the cards because of the variable and nlpResultVariable properties that you set for the System.CommonResponse component.

  8. Click Reset and then close the Skill Tester.
    The card layout
    Description of the illustration.

Add Keyword Support to the Cards

When working with card layouts, it is not the card you select to perform a selection, but the button(s) displayed on a card. In this tutorial, each card displays a single button for ordering the displayed pizza. Following the next steps, you will implement keyword support for buttons in the cards.

  1. In the actions property of the orderPizza state, replace the value for the labelproperty with the following string:
    "Order
    (${enumValue[0]?upper_case}${enumValue[1]?upper_case})${enumValue?keep_after(enumValue[1])}"
  2. Add the following value to the keyword property:
    "${enumValue[0]?upper_case}${enumValue[1]?upper_case}"

    When you're done, the orderPizza state should look like this:

      orderPizza:
        component: "System.CommonResponse"
        properties:
          processUserMessage: true
          variable: "pizzaType"
          nlpResultVariable: "iResult"
          metadata: 
            responseItems:         
            - type: "cards" 
              cardLayout: "horizontal"
              cards:
              - title: "${enumValue}"
                description: "${pizzaCardInfo.value[enumValue].description}"
                imageUrl: "${pizzaCardInfo.value[enumValue].image}"
                iteratorVariable: "pizzaType.type.enumValues"
                rangeStart:
                rangeSize:
                actions:
                - label: "Order (${enumValue[0]?upper_case}${enumValue[1]?upper_case})${enumValue?keep_after(enumValue[1])}"
                  type: "postback"
                  keyword: "${enumValue[0]?upper_case}${enumValue[1]?upper_case}" 
                  payload: 
                    action: "selectPizza"  
                    variables: 
                      pizzaType: "${enumValue}" 
        transitions:
          next: "askSize"
          actions:
            selectPizza: "askSize"

Test the Keyword Support

  1. Click Skill tester icon to open the Skill Tester.
  2. Enter I want to order a pizza.

    Note the CH in the Order (CH)EESE Basic card button label.

  3. Enter CH.
  4. When prompted, select Large. The skill then outputs a confirmation message for a large cheese pizza.
  5. Click Reset.
  6. Enter I want to order a pizza.
  7. When the Cheese Basic pizza card display, enter pe.
  8. When the skill prompts you for a size, enter l. The skill then outputs a confirmation message for a large pepperoni pizza.
    The keywords
    Description of the illustration.

    There are two observations with this latest exercise. Firstly, the keyword uses two letters to make a pizza selection. This is because two pizzas, Pepperoni and Premium, both start with a "P" as the first character. So a single letter keyword does not work. Secondly, the keyword selection does work even if a card is not displayed. Note, however, that if you use page ranging to reduce the number of cards that are rendered at any one time (some messengers like Facebook require this), then only the cards that can be accessed with a keyword shortcut are the ones that are within the current page range.


section 5Summary

The System.CommonResponse component is a powerful system component that skill designers can use to build simple and complex skill responses. This tutorial provided a basic overview of the System.CommonResponse component that gets you started in your skill UI development. The dialog flow component templates contain pre-defined dialog flow configurations you can use as starters. The component templates also provide comments for each System.CommonResponse component property for you to learn about and try.

Though the System.CommonResponse component is powerful, it does not mean that it is the only tool in your box. As you used the System.Output component to confirm the ordered pizza, you should also spend some time exploring the other user interface components in Oracle Digital Assistant.


more informationWant to Learn More?