@rundexter/dexter-interactive
v0.0.1
Published
Facilitate interactivity between a webpage and a Dexter chatbot
Downloads
27
Maintainers
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:
- Initializes the Dexter web widget (note that this loads and downloads a separate script)
- Sets up a series of
handler
s 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 todist/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 buildnpm run analyze:production
will help find candidates for optimizations
Contributing
We'd love to have you contribute! Here's some rough guidelines:
- All changes must pass the built-in lint.
- Any new functionality should have a basic unit test or two
- 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