But even if you are currently not interested in deploying your voice application to multiple voice assistants, you possibly still should take a look at AssistantJS. On one hand obviously because you never know if your platform requirements may change in future. On the other hand, you probably don't want to miss some of the AssistantJS development features:
- Object oriented state machine: AssistantJS uses object oriented states to describe the flow of voice applications. Amongst other things, this enables you to DRY-up your code by using inheritance and mixins. In addition, AssistantJS is heavily based on dependency injection using inversify with all its advantages.
- Extendable: AssistantJS is built out of multiple components extending each other using inversify-components. You are able to decide which components you need and which you don't. See below for an overview of currently implemented AssistantJS components.
- Testable: AssistantJS allows you to write voice applications which are fully testable, even across multiple voice assistants. To make testing even easier, AssistantJS gives you some nice test and mock helpers.
- I18n integration: Thanks to i18next, AssistantJS gives you full multi language support. In addition, it applies some really nice convention-over-configuration rulesets to speed up your development and help you to build better user interfaces using response text variation out of the box.
- Utterance generation: AssistantJS recognizes the intents you are using and enables you to use a template language to generate utterances efficiently. You are tired of maintaining your intents and utterances in dialogflow and in alexa? AssistantJS generates a fitting configuration for alexa and a zip file for dialogflow based you the code you write!
- Logging: AssistantJS uses the awesome bunyan module to give you production-ready and request-specific logging options.
- CLI: AssistantJS gives you a simple command line interface you can use to start your AssistantJS server (
assistant s
, backend by express) or generate nlu configurations (assistant g
). - Entity validation: Don't check for presence of entities, let AssistantJS do this job for you. (Optional dependency)
- Authentication: Protect your states with configurable authentication mechanisms. (Optional dependency)
AssistantJS ecosystem
- assistant-source: AssistantJS core framework, the only real dependency to use AssistantJS. (Current repository)
- assistant-alexa: Enables integrating Amazon Alexa into AssistantJS.
- assistant-apiai: Connects Api.ai (now "Dialogflow") with AssistantJS.
- assistant-google: Brings Google Assistant (via dialogflow) to AssistantJS.
- assistant-validations: Enables you to use a
@needs
decorator to express dependent entities. AssistantJS will automatically manage prompting for this entity, so you can focus on your real business. - assistant-authentication: Enables you to protect your states with configurable authentication stratgies. Currently supports OAuth authentication token and pin authentication.
- assistant-generic-utterances: Automatically inserts useful utterances for generic intents, if a specific platform (like google assistant / dialogflow) does not have generic intents on their own.
Getting started
Install AssistantJS using npm as a global module:
npm i --global assistant-source
Check out these resources to get started:
- video tutorial: A short video tutorial covering the implementation of a bus travelling application. Check this out first!
- wiki: In our github wiki, most of the AssistantJS functionalities are well-described. Look into it for a deeper understanding of AssitantJS!
- assistant-bootstrap: A well documented AssistantJS demo application, which also includes jasmine tests.
- gitter: If you have any additional questions, don't hesitate to ask them on our official gitter channel!
Show some code!
Just to give you a first insight into AssistantJS, this is one of the states implemented in our video tutorial:
@injectable()
// Need account linking? Just add @authenticate(OAuthStrategy) over here!
export class MainState extends ApplicationState {
/* Invoked by saying "Launch/Talk to my bus application" */
invokeGenericIntent(machine: Transitionable) {
this.prompt(this.t());
}
/* "Whats the next bus to train station?" */
@needs("target") // Tell AssistantJS to wait for entity "target"
async busRouteIntent(machine: Transitionable) {
await machine.transitionTo("BusOrderState");
const usersTarget = this.entities.get("target") as string;
this.prompt(this.t({target: usersTarget}));
}
}
Wondering about the empty this.t()
calls? Translation keys are matched by applying simple convention over configuration rules. Try to find them out on your own by taking a look at the corresponding translation file:
{
"mainState": {
"invokeGenericIntent": {
"alexa": "Welcome, Alexa user!",
"google": [
"Welcome, Google user!",
"Nice to have you here, Googler!"
]
},
"busRouteIntent": [
"{Love to help you!|} The next bus to {{target}} arrives in {{minutes}} minutes. Do you want me to buy a ticket?"
],
"helpGenericIntent": "Currently, you can only ask for busses. Try it out!"
},
"busOrderState": {
"yesGenericIntent": "{Allright|Okay}! Just sent a ticket to your smartphone!",
"noGenericIntent": "Cancelled, but maybe next time!",
"helpGenericIntent": "Say \"yes\" to buy a ticket, or \"no\" otherwise."
},
}
As you can see, AssistantJS supports you in building more varied voice applications per default. Just use our template syntax ({Allright|Okay}
) or pass all alternatives in an array. Thanks to our convention over configuration rulesets, we are greeting google assistant users different than alexa users. We could even greet them device-specific thanks to these conventions. Oh, and as you can see, inheriting intents (like the helpGenericIntent
above) from other states (here: ApplicationState
) is also possible.
This is what a test (yes, you can test all assistantjs applications without hassle) for the MainState's invokeGenericIntent
could look like:
describe("MainState", function () {
beforeEach(function(this: ThisContext) {
this.specHelper.prepareSpec(this.defaultSpecOptions);
});
describe("on platform = alexa", function() {
beforeEach(function() {
this.platforms.current = this.platforms.alexa;
});
describe("invokeGenericIntent", function() {
beforeEach(async function() {
await this.specHelper.prepareIntentCall(this.platforms.current, "invokeGenericIntent");
this.responseResults = await this.specHelper.runMachineAndGetResults("MainState");
});
it("greets the user", function() {
expect(this.responseResults.voiceMessage!.text).toEqual("Welcome, Alexa user!")
});
it("waits for an answer", function() {
expect(this.responseResults.shouldSessionEnd).toBeFalsy();
});
});
});
});