npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rundexter/dexter-interactive

v0.0.1

Published

Facilitate interactivity between a webpage and a Dexter chatbot

Downloads

27

Readme

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