Nebulous Pumpkin Moons

    @rundexter/dexter-interactive

    0.0.1 • Public • Published

    Dexter Interactive

    Overview

    Dexter is a powerful chatbot authoring platform that lets you write and deploy your own conversational interfaces using SMS, Facebook, your website, and more.

    While most platforms have deep restrictions on how a bot can and should behave, those deployed on your website should allow for a much broader range of creative integrations. This library is here to help you bring those creative bots to life. It acts as an alternative loader for the Dexter web widget that makes it easy for your website deeply interact with your bot. This is a UMD library, so it can be used in any number of front-end setups.

    Examples

    Example 1 - Minimalistic emoji

    It's simple to let your page react to your bot's conversation. Here's a very literal example - when the bot reports that the user is happy, the page looks happy. When your user is sad, it's sad back.

    First, let's look at what the bot is doing:

    + [*] (good|great|excellent) [*]
    - ^metadata({"vibe":"good"}) Glad to hear it!
    
    + [*] (ok|decent|meh) [*]
    - ^metadata({"vibe": "neutral"}) Well, hopefully tomorrow is a little brighter.
    
    + [*] (awful|bad|terrible) [*]
    - ^metadata({"vibe": "bad"}) Ugh, sorry to hear that.
    

    All we're doing is capturing a few mood words and responding appropriately inside the bot. More importantly, we're adding some metadata to the message that explicitly identifies the vibe we caught. This metadata doesn't actually appear to the user, but your interactive script on your page will be able to see and act on it. Here's the script:

    function setEmoji(chr) {
      document.getElementById('current-emoji').innerHTML = chr;
    }
    window.onload = () => {
      // We'll use the createInterativeBot helper to wire up the whole bot at once
      dexterInteractive.createInteractiveBot({
        // We don't care what the user says for this example, just what the bot says.
        handleOutgoing: false
        // Let's see all the logs in the console.  Turn this off in production.
        , logger: true
        // Translate each incoming vibe to an emoji
        , handler: [
          {metaPath: '0.vibe',   meta: 'good',    onMatch: () => setEmoji('😀')}
          , {metaPath: '0.vibe', meta: 'neutral', onMatch: () => setEmoji('😐')}
          , {metaPath: '0.vibe', meta: 'bad',     onMatch: () => setEmoji('😔')}
        ]
        // This is the usual config from the webhook
        , dexterSettings: {
          botId: '0b755674-95a4-4d1f-a8f6-d361920e5d5b'
          , botTitle: 'Example 01 - Minimal'
          , baseUrl: 'https://bots.rundexter.com'
          , url: 'https://rundexter.com/webwidget-beta'
        }
      });
    };

    The real work is done in the "handlers" collection, where we translate vibes we recognize into an emoji we want to display. We pull the vibe out of the metadata we shipped along with the response via "0.vibe". The 0 means we only care about the first ^metadata() (there can be more than 1 if desired), and "vibe" is the property we want to check. Once we have the vibe, we check its value via explicit matching, so nothing happens if we don't see "good", "bad", or "neutral", or if the response doesn't include "vibe" metadata.

    Example 2 - Browsing assistance

    We can take interaction a step further and actually control our website from the bot. Let's say you've got a huge database of something...products, software, or maybe encyclopedia articles. Your bot can be another discovery and conversion tool in your arsenal, letting you turn natural language requests into hand-curated results. Example 2 is a simple skeleton of how such an interaction could work.

    The bot side of things is pretty simple - we prompt the user to choose a topic and a subtopic, then pass the choices along to the bot via metadata:

    // Kick off the process
    + go
    - What broad category are you interested in? ^buttons("People",  "History", "Geography", "Arts", "Philosophy", "Everyday life", "Society", "Health", "Science", "Technology", "Mathematics")
    
    + people
    - <set level1=People> ^metadata({"level1": "People"}) ^buttons("Leaders", "Religious", "Philosophers", "Writers", "Musicians", "Scientists", "Mathematicians", "Artists", "Filmmakers", "Businesspeople")
    
    + leaders
    - ^metadata({"level1": "<get level1>", "level2": "<sentence>"}) Great, let's see what we have for you.
    

    This strategy simply prompts the user to choose a primary and secondary category, then passes the selections along to the page. Other, more interesting strategies could be easily swapped in here - things like search, mood quizzes, recommendation funnels, limited time discount prompts, and more. Ultimately, all your page cares about is that it gets a directive on which subcategory to show:

    function showLevel2(level1, level2) {
      const matched1 = data[level1]
        , matched2 = matched1 ? matched1[level2] : null
      ;
      if (matched2) {
        showArticles(matched2);
      } 
    }
    function showArticles(articles) {
      // ...
      articles.forEach((article) => {
        const el = document.createElement('div');
        el.innerHTML = `
          <div>
            <h3><a href="${article.url}" title="Read about ${article.title} on Wikipedia">${article.title}</a></h3>
            <figure>
              <img src="${article.image}" title="${article.title}">
              <figcaption>${article.summary}</figcaption>
            </figure>
          </div>
        `;
        articleContainer.append(el);
      });
    }
    window.onload = () => {
    {
      dexterInteractive.createInteractiveBot({
        handleOutgoing: false
    	, handler: [
    	  {metaPath: '0.level2', meta: /.+/, onMatch: (type, text, metadata) => {
            showLevel2(metadata[0].level1, metadata[0].level2);
    	  }}
    	]
        // ...
      });
    };

    This snippet roughly shows how the interface works - when we get a level2 signal in a bot response, we find the matching articles and render them to the screen. The showArticles method could easily be replaced with whatever routing strategy your application may use, be it a traditional window.location assignment or via deep integration with your favorite single-page application tooling.

    Getting started

    Loading the library

    Dexter Interactive is a UMD library, so there are a variety of ways you can include it in your project. You can include it directly on your page via a script tag:

    <script type="text/javascript" src=""></script>

    Add it to your Webpack or ES6 project via import:

    npm install dexter-interactive
    import {createInteractiveBot} from 'dexter-interactive';

    or use an AMD/CommonJS require:

    npm install dexter-interactive
    var DexterInteractive = require('node-modules/dexter-interactive/dist/dexter-interactive')
      , createInteractiveBot = DexterInteractive.createInteractiveBot
    ;

    Initializing the bot

    Once you have a handle on the createInteractiveBot function, call it:

    dexterInteractive.createInteractiveBot({
      // Set up your interactions
      handler: []
      // Initialize your bot
      , dexterSettings: {
        botId: '2bdd49db-4f52-466b-910a-7ab7c735ffdf'
        , botTitle: 'Example 02 - Browsing'
        , baseUrl: 'https://bots.rundexter.com'
        , url: 'https://rundexter.com/webwidget-beta'
      }
    });

    createInteractiveBot does 2 things:

    1. Initializes the Dexter web widget (note that this loads and downloads a separate script)
    2. Sets up a series of handlers to help you filter and route incoming messages and metadata Note that all of the configuration settings available in the widget are available in your dexterSettings.

    Creating handlers

    You can have as few or as many handlers in your handler collection as you'd like. Only one handler can be triggered per message, and the first handler to match the message wins. Handlers get evaluated in the order they're written.

    There are three ways to trigger a handler: by message text, by message metadata, or by a custom function. Each of these triggers can be tested using several mechanisms:

    Text-based handlers

    Text-based handlers simply test the raw text of the message to see if it should be acted on. The text is not modified in any way.

    Test by exact match

    handler: [
      {text: 'hello', onMatch: () => console.log('Hi!')}
    ]

    Test by RegExp

    handler: [
      {text: /register/, onMatch: () => alert('Glad to have you!')}
    ]

    Test by function

    handler: [
      {text: (msg) => ['hi', 'hello'].indexOf(msg) >= 0, onMatch: () => console.log('Hi!')}
    ]

    Metadata-based handlers

    Bots can pass explicit metadata along with a response by using the ^metadata({"foo": "bar"}) shortcode. Your handler can test for specific metadata by first using a metaPath to target a specific value via a path declaration, then testing that value in meta. The test even runs if no data is found in the path unless a RegExp is used, in which case any non-string value will automatically fail.

    Test by exact match

    handler: [
      {metaPath: '0.user.subscribed', meta: 1, onMatch: () => console.log('User is subscribed')}
    ]

    Test by RegExp

    handler: [
      {metaPath: '0.user.email', meta: /@aol\.com/, onMatch: () => console.log('Really?!??')}
    ]

    Test by function

    handler: [
      {metaPath: '0.user.status', meta: (val) => ['paid', 'comped'].indexOf(val) >= 0, onMatch: () => console.log('User is up-to-date on payments')}
    ]

    Function-based handler

    Function handlers are different in that they replace the entire handler array - you can't mix and match a function-based handler with other handler types.

    handler: (type, text, metadata, payload) => {
      if (type === PAYLOAD_TYPE_BOT) {
        switch(_.get(metadata, '0.name')) {
    	  // ...
    	}
      } else {
        console.log('User said', text);
      }
    }

    API

    createInteractiveConfig(cfg)

    // All possible settings shown with all default values
    createInteractiveConfig({
      // If true, messages sent by the bot will be fed to your handler, otherwise they will be ignored.
      handleBotEvents: true
      // If true, messages sent by your user will be fed to your handler, otherwise they will be ignored.
      , handleUserEvents: true
      // If true, messages sent while the chat widget is closed will be fed to your handler, including any historical messages sent when the widget is initialized.
      , handleWhenClosed: false
      // Routes logs that can help with debugging. There are 3 possible values:
      // true: routes to the correct console.* function (i.e. errors to console.error, warnings to console.warn, etc.)
      // function (level, msg, [metadata]) {}: level is one of the LOG_LEVEL constants, msg is the plain text of the log, metadata is an optional object containing additional details
      // false: disables library logging completely
      , logger: false
      // REQUIRED: Any event handlers used to control interactivity
      , handlers: []
      // These are the values that are used to configure your widget
      , dexterSettings: {
          // REQUIRED: The ID of the bot to load (you can get it from the URL in the editor or from your existing embed HTML)
          botId: '' // Example: AABBCC123123
    	  // REQUIRED: How you want the bot's title to appear in the widget
    	  , botTitle: '' // Example: My Best Botty
    	  // REQUIRED: Leave this as is.
    	  , baseURL: 'https://bots.rundexter.com'
    	  // REQUIRED: Leave this as is.
    	  , url: 'https://rundexter.com/webwidget-beta'
    	  // Selector string for a custom launcher button for the widget
          , customLauncher: null // Example: '#link'
          // A boolean indicating whether to hide the default launcher button
          , hideDefaultLauncher: false
          // A custom icon on the top left of the message panel
          , logoIcon: null // Example: 'https://example.com/logo.png' 
          // A custom icon for the default launcher button that opens the widget
          , openIcon: null // Example: 'https://example.com/button-open.png' 
          // A custom icon for the default launcher button that closes the widget
          , closeIcon: null // Example: 'https://example.com/button-close.png' 
          // Event called when the widget is loaded - can be used to bind the API outside this library
          , onLoad: function (api) { }
          // Event called when the widget is opened
          , onOpen: function (api) { }
          // Event called when the widget is closed
          , onClose: function (api) { }
      }
    })

    For a minimal config to copy/paste:

    createInteractiveConfig({
      handlers: []
      , dexterSettings: {
          botId: '' // Example: AABBCC123123
    	  , botTitle: '' // Example: My Best Botty
    	  , baseURL: 'https://bots.rundexter.com'
    	  , url: 'https://rundexter.com/webwidget-beta'
      }
    })

    Constants

    Logger constants - passed to cfg.logger functions as type

    Constant Description
    LOG_LEVEL_DEBUG A low-priority debug log for development - shows the results of all handler decisions
    LOG_LEVEL_INFO Useful information for day-to-day monitoring - shows the final handler decision
    LOG_LEVEL_WARN Non-breaking errors such as configuration issues
    LOG_LEVEL_ERROR Critical errors

    Message payload types - passed to cfg.handler functions as type

    Constant Description
    PAYLOAD_TYPE_BOT A response sent from the bot
    PAYLOAD_TYPE_USER A request sent by the user

    Development

    Working with examples

    All our examples are created with vanilla tooling to make it easy for anyone to sit down and play with them. Getting started is simple:

    npm install
    npm run examples

    That's it! Calling npm run examples will build the library and start up a webserver at http://localhost:5000 to show the examples. You can use our bots freely, or use the code in each examples' /bots directory to create your own. Just copy each file into its respective topic, deploy the widget, and replace the botId in the example's dexterSettings object, and away you go!

    A few quick notes:

    • While we designed the library to have very broad browser compatability, the examples may require a more modern browser such as Firefox, Chrome, or Edge to view.
    • The data in example 2 can be rebuilt if needed or desired - see its /bin folder for details.

    Editing the code

    We've got a few commands baked into the library to help you with development:

    • npm run watch and link to dist/dexter-interactive.js to work in real time. This won't auto-reload your page on save, but it will update the script.
    • npm run build:production generates a build
    • npm run analyze:production will help find candidates for optimizations

    Contributing

    We'd love to have you contribute! Here's some rough guidelines:

    1. All changes must pass the built-in lint.
    2. Any new functionality should have a basic unit test or two
    3. Make sure you include your reasons for submitting the PR in its description

    Looking for some ideas on what to contribute?

    • More or better tests are good
    • More examples are good
    • Shrinking library without sacrificing compatibility would be amazing
    • Alternate matching strategies will be considered with a solid rationale

    Install

    npm i @rundexter/dexter-interactive

    DownloadsWeekly Downloads

    1

    Version

    0.0.1

    License

    MIT

    Unpacked Size

    1.12 MB

    Total Files

    35

    Last publish

    Collaborators

    • rundexter-admin