Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

prague

Package Overview
Dependencies
Maintainers
1
Versions
91
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

prague - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

4

dist/Chat.d.ts

@@ -24,3 +24,3 @@ import { Observable } from 'rxjs';

export declare const getAddress: (message: Message) => Address;
export interface IChatSession {
export interface IChatInput {
text: string;

@@ -34,3 +34,3 @@ chat: UniversalChat;

export interface IChat<S> {
session$: Observable<S>;
input$: Observable<S>;
}

@@ -1,2 +0,2 @@

import { ITextSession, Handler, Rule } from './Rules';
import { ITextInput, Action, Rule } from './Rules';
export interface LuisCredentials {

@@ -7,7 +7,7 @@ name: string;

}
export interface LuisRule<S extends ITextSession> {
export interface LuisRule<S extends ITextInput> {
intent: string;
handler: Handler<S>;
action: Action<S>;
}
export declare class LUIS<S extends ITextSession> {
export declare class LUIS<S extends ITextInput> {
private models;

@@ -18,4 +18,4 @@ constructor(...creds: LuisCredentials[]);

private call(modelName, utterance);
intent(intent: string, handler: Handler<S>): LuisRule<S>;
intent(intent: string, action: Action<S>): LuisRule<S>;
rule(modelName: string, luisRules: LuisRule<S>[], threshold?: number): Rule<S>;
}

@@ -87,10 +87,10 @@ "use strict";

};
LUIS.prototype.intent = function (intent, handler) {
LUIS.prototype.intent = function (intent, action) {
return {
intent: intent,
handler: handler
action: action
};
};
// "classic" LUIS usage - for a given model, say what to do with each intent above a given threshold
// IMPORTANT: the order of rules is not important - the handler for the *highest-ranked intent* will be executed
// IMPORTANT: the order of rules is not important - the action for the *highest-ranked intent* will be executed
LUIS.prototype.rule = function (modelName, luisRules, threshold) {

@@ -100,8 +100,8 @@ var _this = this;

return {
recognizer: function (session) {
return _this.call(modelName, session.text)
matcher: function (input) {
return _this.call(modelName, input.text)
.flatMap(function (luisResult) {
return rxjs_1.Observable.from(luisResult)
.filter(function (match) { return match.threshold >= threshold; })
.filter(function (match) { return luisRules.some(function (luisRule) { return luisRule.intent === match.intent; }); })
.filter(function (matcher) { return matcher.threshold >= threshold; })
.filter(function (matcher) { return luisRules.some(function (luisRule) { return luisRule.intent === matcher.intent; }); })
.take(1);

@@ -111,4 +111,4 @@ } // take the highest ranked intent in our rule list

},
handler: function (session, args) {
return luisRules.find(function (luisRule) { return luisRule.intent === args.intent; }).handler(session, args.entities);
action: function (input, args) {
return luisRules.find(function (luisRule) { return luisRule.intent === args.intent; }).action(input, args.entities);
},

@@ -115,0 +115,0 @@ name: "LUIS: " + modelName + "/" + luisRules.map(function (lr) { return lr.intent; }).join('+')

@@ -10,3 +10,3 @@ // Eventually this libary might be factored into multiple libraries/plugins. Until then, this

__export(require("./Rules"));
// Recognizers
// Matchers
__export(require("./LUIS"));

@@ -19,4 +19,4 @@ __export(require("./RegExp"));

__export(require("./Connectors/DirectLine"));
// Session Providers
// Input Providers
__export(require("./ReduxChat"));
//# sourceMappingURL=prague.js.map

@@ -1,39 +0,19 @@

import { IChatSession } from './Chat';
import { ITextSession, Handler, Rule } from './Rules';
export interface PromptRules<S extends ITextSession & IChatSession> {
[promptKey: string]: Rule<S>;
import { IChatInput } from './Chat';
import { ITextInput, Action, Matcher, Result, Rule } from './Rules';
export interface PromptStuff<S> {
matcher: Matcher<S>;
action: Action<S>;
creator: (input: S) => Result<any>;
}
export interface PromptRulesMaker<S extends ITextSession & IChatSession> {
(prompt: Prompt<S>): PromptRules<S>;
}
export declare type Choice = string;
export declare type ChoiceList = Choice[];
export interface ChoiceLists {
[choiceKey: string]: ChoiceList;
}
export declare class Prompt<S extends ITextSession & IChatSession> {
private choiceLists;
private promptRulesMaker;
export declare class Prompt<S extends ITextInput & IChatInput> {
private getPromptKey;
private setPromptKey;
private promptRules;
constructor(choiceLists: ChoiceLists, promptRulesMaker: PromptRulesMaker<S>, getPromptKey: (session: S) => string, setPromptKey: (session: S, promptKey?: string) => void);
text(handler: Handler<S>): Rule<S>;
choice(choiceName: string, handler: Handler<S>): {
recognizer: (session: any) => {
choice: string;
};
handler: Handler<S>;
};
confirm(handler: Handler<S>): {
recognizer: (session: any) => {
confirm: boolean;
};
handler: Handler<S>;
};
private prompts;
constructor(getPromptKey: (input: S) => string, setPromptKey: (input: S, promptKey?: string) => void);
add(promptKey: string, promptStuff: PromptStuff<S>): void;
text(promptKey: string, text: string, action: Action<S>): void;
choice(promptKey: string, text: string, choices: string[], action: Action<S>): void;
confirm(promptKey: string, text: string, action: Action<S>): void;
rule(): Rule<S>;
textCreate(session: S, promptKey: string, text: string): void;
choiceCreate(session: S, promptKey: string, choiceName: string, text: string): void;
private yorn;
confirmCreate(session: S, promptKey: string, text: string): void;
reply(promptKey: string): (input: S) => any;
}

@@ -5,48 +5,78 @@ "use strict";

var Prompt = (function () {
function Prompt(choiceLists, promptRulesMaker, getPromptKey, setPromptKey) {
this.choiceLists = choiceLists;
this.promptRulesMaker = promptRulesMaker;
function Prompt(getPromptKey, setPromptKey) {
this.getPromptKey = getPromptKey;
this.setPromptKey = setPromptKey;
this.yorn = ['Yes', 'No'];
this.promptRules = promptRulesMaker(this);
this.prompts = {};
}
Prompt.prototype.add = function (promptKey, promptStuff) {
if (this.prompts[promptKey]) {
console.warn("Prompt key " + promptKey + " already exists. Plese use a different key.");
return;
}
this.prompts[promptKey] = promptStuff;
};
// Prompt Rule Creators
Prompt.prototype.text = function (handler) {
return {
recognizer: function (session) { return ({ text: session.text }); },
handler: handler
};
Prompt.prototype.text = function (promptKey, text, action) {
var _this = this;
this.add(promptKey, {
matcher: function (input) { return input.text; },
action: action,
creator: function (input) {
_this.setPromptKey(input, promptKey);
input.reply(text);
}
});
};
Prompt.prototype.choice = function (choiceName, handler) {
Prompt.prototype.choice = function (promptKey, text, choices, action) {
var _this = this;
return {
recognizer: function (session) {
var choice = _this.choiceLists[choiceName].find(function (choice) { return choice.toLowerCase() === session.text.toLowerCase(); });
return choice && { choice: choice };
},
handler: handler
};
this.add(promptKey, {
matcher: function (input) { return choices.find(function (choice) { return choice.toLowerCase() === input.text.toLowerCase(); }); },
action: action,
creator: function (input) {
_this.setPromptKey(input, promptKey);
input.reply({
type: 'message',
from: { id: 'MyBot' },
text: text,
suggestedActions: { actions: choices.map(function (choice) { return ({
type: 'postBack',
title: choice,
value: choice
}); }) }
});
}
});
};
Prompt.prototype.confirm = function (handler) {
return {
recognizer: function (session) {
var confirm = session.text.toLowerCase() === 'yes'; // TO DO we can do better than this
return { confirm: confirm };
},
handler: handler
};
Prompt.prototype.confirm = function (promptKey, text, action) {
var _this = this;
this.add(promptKey, {
matcher: function (input) { return input.text.toLowerCase() === 'yes'; },
action: action,
creator: function (input) {
_this.setPromptKey(input, promptKey);
input.reply({
type: 'message',
from: { id: 'MyBot' },
text: text,
suggestedActions: { actions: ['Yes', 'No'].map(function (choice) { return ({
type: 'postBack',
title: choice,
value: choice
}); }) }
});
}
});
};
Prompt.prototype.rule = function () {
var _this = this;
return Rules_1.filter(function (session) { return _this.getPromptKey(session) !== undefined; }, {
recognizer: function (session) {
console.log("prompt looking for", _this.getPromptKey(session));
var rule = _this.promptRules[_this.getPromptKey(session)];
return rule && rule.recognizer(session);
return Rules_1.filter(function (input) { return _this.getPromptKey(input) !== undefined; }, {
matcher: function (input) {
console.log("prompt looking for", _this.getPromptKey(input));
var rule = _this.prompts[_this.getPromptKey(input)];
return rule && rule.matcher(input);
},
handler: function (session, args) {
var rule = _this.promptRules[_this.getPromptKey(session)];
_this.setPromptKey(session, undefined);
return rule.handler(session, args);
action: function (input, args) {
var action = _this.prompts[_this.getPromptKey(input)].action;
_this.setPromptKey(input, undefined);
return action(input, args);
},

@@ -56,36 +86,5 @@ name: "PROMPT"

};
// Prompt Message Creators -- feels like these maybe belong in the connectors?
Prompt.prototype.textCreate = function (session, promptKey, text) {
this.setPromptKey(session, promptKey);
session.reply(text);
Prompt.prototype.reply = function (promptKey) {
return this.prompts[promptKey].creator;
};
Prompt.prototype.choiceCreate = function (session, promptKey, choiceName, text) {
var choiceList = this.choiceLists[choiceName];
if (!choiceList)
return;
this.setPromptKey(session, promptKey);
session.reply({
type: 'message',
from: { id: 'RecipeBot' },
text: text,
suggestedActions: { actions: choiceList.map(function (choice) { return ({
type: 'postBack',
title: choice,
value: choice
}); }) }
});
};
Prompt.prototype.confirmCreate = function (session, promptKey, text) {
this.setPromptKey(session, promptKey);
session.reply({
type: 'message',
from: { id: 'RecipeBot' },
text: text,
suggestedActions: { actions: this.yorn.map(function (choice) { return ({
type: 'postBack',
title: choice,
value: choice
}); }) }
});
};
return Prompt;

@@ -92,0 +91,0 @@ }());

import { Observable } from 'rxjs';
import { ITextSession } from './Rules';
import { UniversalChat, Message, Activity, Address, IChatSession, IChat } from './Chat';
import { ITextInput } from './Rules';
import { UniversalChat, Message, Activity, Address, IChatInput, IChat } from './Chat';
import { Store } from 'redux';
import { IStateSession } from './State';
export interface ReduxSession<APP, BOTDATA> extends IStateSession<BOTDATA> {
import { IStateInput } from './State';
export interface ReduxInput<APP, BOTDATA> extends IStateInput<BOTDATA> {
state: APP;

@@ -11,3 +11,3 @@ store: Store<APP>;

}
export declare class ReduxChatSession<APP, BOTDATA> implements ITextSession, IChatSession, ReduxSession<APP, BOTDATA> {
export declare class ReduxChatInput<APP, BOTDATA> implements ITextInput, IChatInput, ReduxInput<APP, BOTDATA> {
message: Message;

@@ -25,8 +25,8 @@ chat: UniversalChat;

}
export declare class ReduxChat<APP, BOTDATA> implements IChat<ReduxChatSession<APP, BOTDATA>> {
export declare class ReduxChat<APP, BOTDATA> implements IChat<ReduxChatInput<APP, BOTDATA>> {
private chat;
private store;
private getBotData;
session$: Observable<ReduxChatSession<APP, BOTDATA>>;
input$: Observable<ReduxChatInput<APP, BOTDATA>>;
constructor(chat: UniversalChat, store: Store<APP>, getBotData: (state: APP) => BOTDATA);
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Chat_1 = require("./Chat");
var ReduxChatSession = (function () {
function ReduxChatSession(message, chat, store, getBotData) {
var ReduxChatInput = (function () {
function ReduxChatInput(message, chat, store, getBotData) {
this.message = message;

@@ -15,11 +15,11 @@ this.chat = chat;

}
ReduxChatSession.prototype.reply = function (activity) {
ReduxChatInput.prototype.reply = function (activity) {
this.chat.send(this.address, activity);
};
ReduxChatSession.prototype.replyAsync = function (activity) {
ReduxChatInput.prototype.replyAsync = function (activity) {
return this.chat.sendAsync(this.address, activity);
};
return ReduxChatSession;
return ReduxChatInput;
}());
exports.ReduxChatSession = ReduxChatSession;
exports.ReduxChatInput = ReduxChatInput;
var ReduxChat = (function () {

@@ -31,5 +31,5 @@ function ReduxChat(chat, store, getBotData) {

this.getBotData = getBotData;
this.session$ = chat.activity$
this.input$ = chat.activity$
.filter(function (activity) { return activity.type === 'message'; })
.map(function (message) { return new ReduxChatSession(message, _this.chat, _this.store, _this.getBotData); });
.map(function (message) { return new ReduxChatInput(message, _this.chat, _this.store, _this.getBotData); });
}

@@ -36,0 +36,0 @@ return ReduxChat;

@@ -1,8 +0,8 @@

import { ITextSession, Handler, Rule } from './Rules';
import { ITextInput, Action, Rule } from './Rules';
export interface REArgs {
groups: RegExpExecArray;
}
export declare class RE<S extends ITextSession> {
export declare class RE<S extends ITextInput> {
constructor();
rule(intents: RegExp | RegExp[], handler: Handler<S>): Rule<S>;
rule(intents: RegExp | RegExp[], action: Action<S>): Rule<S>;
}

@@ -8,9 +8,9 @@ "use strict";

}
// Either call as re(intent, handler) or test([intent, intent, ...], handler)
RE.prototype.rule = function (intents, handler) {
// Either call as re(intent, action) or test([intent, intent, ...], action)
RE.prototype.rule = function (intents, action) {
return {
recognizer: function (session) {
matcher: function (input) {
return rxjs_1.Observable.from(Rules_1.arrayize(intents))
.map(function (regexp) { return regexp.exec(session.text); })
.filter(function (groups) { return groups && groups[0] === session.text; })
.map(function (regexp) { return regexp.exec(input.text); })
.filter(function (groups) { return groups && groups[0] === input.text; })
.take(1)

@@ -20,3 +20,3 @@ .map(function (groups) { return ({ groups: groups }); })

},
handler: handler,
action: action,
name: "REGEXP"

@@ -23,0 +23,0 @@ };

import { Observable } from 'rxjs';
export interface ITextSession {
export interface ITextInput {
text: string;
}
export interface Recognizer<S> {
(session: S): any | Observable<any>;
export declare type Result<T> = T | Observable<T> | Promise<T>;
export interface Matcher<S> {
(input: S): Result<any>;
}
export declare const always: <S>(session: S) => Observable<boolean>;
export interface Handler<S> {
(session: S, args?: any): any | Observable<any>;
export declare const always: <S>(input: S) => Observable<boolean>;
export interface Action<S> {
(input: S, args?: any): Result<any>;
}
export interface Rule<S> {
recognizer: Recognizer<S>;
handler: Handler<S>;
matcher: Matcher<S>;
action: Action<S>;
name?: string;
}
export declare const rule: <S>(recognizer: Recognizer<S>, handler: Handler<S>, name?: string) => Rule<S>;
export declare const defaultRule: <S>(handler: Handler<S>) => Rule<S>;
export declare const rule: <S>(matcher: Matcher<S>, action: Action<S>, name?: string) => Rule<S>;
export declare const defaultRule: <S>(action: Action<S>) => Rule<S>;
export declare const arrayize: <T>(stuff: T | T[]) => T[];
export declare const runRecognizer: <S>(session: S, recognizer: Recognizer<S>) => Observable<any>;
export declare const runHandler: <S>(session: S, args: Observable<any>, handler: Handler<S>) => Observable<any>;
export declare const executeRule: <S>(session: S, rule: Rule<S>) => Observable<any>;
export declare const firstMatch: <S>(...rules: Rule<S>[]) => Rule<S>;
export declare const bestMatch: <S>(...rules: Rule<S>[]) => void;
export declare const doMatcher: <S>(input: S, matcher: Matcher<S>) => Observable<any>;
export declare const doAction: <S>(input: S, args: Observable<any>, action: Action<S>) => Observable<any>;
export declare const executeRule: <S>(input: S, rule: Rule<S>) => Observable<any>;
export declare const firstMatcher: <S>(...rules: Rule<S>[]) => Rule<S>;
export declare const bestMatcher: <S>(...rules: Rule<S>[]) => void;
export interface Query<S> {
(session: S): boolean | Observable<boolean>;
(input: S): Result<boolean>;
}

@@ -28,0 +29,0 @@ export interface Queries<S> {

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var rxjs_1 = require("rxjs");
exports.always = function (session) { return rxjs_1.Observable.of(true); };
exports.rule = function (recognizer, handler, name) { return ({
recognizer: recognizer,
handler: handler,
exports.always = function (input) { return rxjs_1.Observable.of(true); };
exports.rule = function (matcher, action, name) { return ({
matcher: matcher,
action: action,
name: name
}); };
exports.defaultRule = function (handler) { return exports.rule(exports.always, handler); };
exports.defaultRule = function (action) { return exports.rule(exports.always, action); };
exports.arrayize = function (stuff) { return Array.isArray(stuff) ? stuff : [stuff]; };

@@ -19,19 +19,19 @@ var observize = function (t) {

};
exports.runRecognizer = function (session, recognizer) {
return observize(recognizer(session))
exports.doMatcher = function (input, matcher) {
return observize(matcher(input))
.filter(function (result) { return !!result; })
.do(function (args) { return console.log("recognizer result", args); });
.do(function (args) { return console.log("matcher result", args); });
};
exports.runHandler = function (session, args, handler) {
console.log("resolving handler");
return observize(handler(session, args))
.do(function (result) { return console.log("handler result", result); })
.take(1); // because handlers may emit more than one value
exports.doAction = function (input, args, action) {
console.log("resolving action");
return observize(action(input, args))
.do(function (result) { return console.log("action result", result); })
.take(1); // because actions may emit more than one value
};
exports.executeRule = function (session, rule) {
return exports.runRecognizer(session, rule.recognizer)
exports.executeRule = function (input, rule) {
return exports.doMatcher(input, rule.matcher)
.do(function (args) { return console.log("rule " + rule.name + " succeeded!", args); })
.flatMap(function (args) { return exports.runHandler(session, args, rule.handler); });
.flatMap(function (args) { return exports.doAction(input, args, rule.action); });
};
exports.firstMatch = function () {
exports.firstMatcher = function () {
var rules = [];

@@ -42,7 +42,7 @@ for (var _i = 0; _i < arguments.length; _i++) {

return ({
recognizer: function (session) {
matcher: function (input) {
return rxjs_1.Observable.from(rules)
.switchMap(function (rule, index) {
console.log("evaluating " + rule.name);
return exports.runRecognizer(session, rule.recognizer)
return exports.doMatcher(input, rule.matcher)
.map(function (args) { return ({ index: index, args: args }); });

@@ -52,7 +52,7 @@ })

},
handler: function (session, args) { return rules[args.index].handler(session, args.args); },
name: "firstMatch of " + rules.length + " rules"
action: function (input, args) { return rules[args.index].action(input, args.args); },
name: "firstMatcher of " + rules.length + " rules"
});
};
exports.bestMatch = function () {
exports.bestMatcher = function () {
var rules = [];

@@ -65,11 +65,11 @@ for (var _i = 0; _i < arguments.length; _i++) {

exports.filter = function (query, rule) { return ({
recognizer: function (session) {
return observize(query(session))
matcher: function (input) {
return observize(query(input))
.filter(function (result) { return !!result; })
.do(function (_) { return console.log(""); })
.flatMap(function (_) { return exports.runRecognizer(session, rule.recognizer); });
.flatMap(function (_) { return exports.doMatcher(input, rule.matcher); });
},
handler: rule.handler,
action: rule.action,
name: "filter rule " + rule.name
}); };
//# sourceMappingURL=Rules.js.map

@@ -1,3 +0,3 @@

export interface IStateSession<BOTDATA> {
export interface IStateInput<BOTDATA> {
data: BOTDATA;
}
{
"name": "prague",
"version": "0.1.0",
"version": "0.2.0",
"description": "experimental Rx-based bot technology",

@@ -5,0 +5,0 @@ "main": "dist/prague.js",

@@ -11,3 +11,3 @@ # Prague

Some types of applications you could build with Prague:
* Command line
* OS shell
* Browser app w/chat interface

@@ -39,3 +39,3 @@ * Browser app w/voice interface

The core concept is the **Recognizer**, which interprets input and extracts out semantic content, and the **Handler** which acts upon that content. Recognizers and Handlers formed a matched pair called a **Rule**.
The core concept is the **Matcher**, which interprets input and extracts out semantic content, and the **Action** which acts upon that content. Matchers and Actions formed a matchered pair called a **Rule**.

@@ -46,4 +46,4 @@ Here is a simple rule which does some extremely limited sentiment analysis and responds accordingly:

const moodRule = {
recognizer: (session: ITextSession) => ({ moods: ['sad', 'mad', 'happy'].filter(mood => session.text.indexOf(mood) != -1) }),
handler: (session: ITextSession, args: { moods: string[] }) => moods.forEach(mood => console.log(`I hear you are feeling ${mood}`))
matcher: (input: ITextInput) => ({ moods: ['sad', 'mad', 'happy'].filter(mood => input.text.indexOf(mood) != -1) }),
action: (input: ITextInput, args: { moods: string[] }) => moods.forEach(mood => console.log(`I hear you are feeling ${mood}`))
}

@@ -58,17 +58,17 @@ ```

### Sessions
### Inputs
The input to both Recognizers and Handlers is called a *Session*, which can take on different forms depending on the application and input source.
The input to both Matchers and Actions is called an *Input*, which can take on different forms depending on the application and input source. It typically includes the original input plus a variety of helper data and functions.
### Built-in recognizers
### Built-in matchers
Prague provides built-in recognizers for *LUIS* models and Regular Expressions. Here is myNameRule rewritten to use the built-in recognizer:
Prague provides built-in matchers for *LUIS* models and Regular Expressions. Here is a new rule called myNameRule which uses the built-in matcher:
```typescript
const re = new RE<ITextSession>();
const re = new RE<ITextInput>();
const myNameRule = re.rule(
/My name is (.*)/,
(session, args) => `Hello, ${args[1]}`
(input, args) => `Hello, ${args[1]}`
)

@@ -78,3 +78,3 @@

const luis = new LUIS<ITextSession>({
const luis = new LUIS<ITextInput>({
name: 'nameForMyModel',

@@ -86,3 +86,3 @@ id: 'myID',

const myNameRule = luis.rule('nameForMyModel', [
luis.intent('myName', (session, args: { name: string }) => `Hello, ${args.name}`)
luis.intent('myName', (input, args: { name: string }) => `Hello, ${args.name}`)
])

@@ -94,13 +94,8 @@

You could write a single rule to respond to every possible input, but a more typical approach is to break the problem down into a series of rules. Here is one that uses Regular Expressions to identify and extract out the user's name.
You could write a single rule to respond to every possible input, but a more typical approach is to break the problem down into a series of rules.
const myNameRule = {
recognizer (session: ITextSession) => /My name is (.*)/.exec(session.text),
handler: (session: ITextSession, args: RegExpExecArray) => `Hello, ${args[1]}`
}
You could call `executeRule` on all of your rules, but most commonly you'll want to stop once one of them succeeds. For this we'll use a helper function called `firstMatcher` which runs through a series of rules in order, calling the matcher for each, stopping at the first one which succeeds, and calling its action.
Now we have two rules, and you can imagine many others. You could call `executeRule` on all of your rules, but most commonly you'll want to stop once one of them succeeds. For this we'll use a helper function called `firstMatch` which runs through a series of rules in order, calling the recognizer for each, stopping at the first one which succeeds, and calling its handler.
```typescript
executeRule({text: "My name is Bill"}, firstMatch(
executeRule({text: "My name is Bill"}, firstMatcher(
moodRule,

@@ -115,8 +110,8 @@ myNameRule,

Note that `firstMatch` itself returns a rule, so you can organize this code differently:
Note that `firstMatcher` itself returns a rule, so you can organize this code differently:
```typescript
executeRule({text: "My name is Bill"}, firstMatch(
executeRule({text: "My name is Bill"}, firstMatcher(
moodRule,
firstMatch(
firstMatcher(
myNameRule,

@@ -133,8 +128,11 @@ jukeboxRule

(Another approach to a list of rules would be to run through all the rules, calling each recognizer, and only calling the handler for the recognizer which returns the *best* match. This approach requires recognizers which return an agreed-upon format (and scale) for scoring matches.)
(Another approach to a list of rules would be to run through all the rules, calling each matcher, and only calling the action for the matcher which returns the *best* matcher. This approach requires matchers which return an agreed-upon format (and scale) for scoring matchers.)
### State and Rules
### Application state vs. Conversation state
Just as our understanding of the world influences how we interpret the things people say to us, and how we then respond, each input should be evaluated in the context of the entire application state.
In an OS shell, the state might include the current directory. If a user says "remove this directory", the action will rely on that state to remove the correct directory. The state might also include data about the conversation itself. If a user says "create a directory named connectors" followed by "delete it", the
### Prompts

@@ -141,0 +139,0 @@

@@ -67,3 +67,3 @@ // Generic Chat support

export interface IChatSession {
export interface IChatInput {
text: string;

@@ -78,3 +78,3 @@ chat: UniversalChat;

export interface IChat<S> {
session$: Observable<S>;
input$: Observable<S>;
}

@@ -1,15 +0,31 @@

interface DialogHandler<S> {
import { Rule } from './Rules';
interface Handler<S> {
(session: S, args: any, next: Function): void;
}
interface Waterfalls<S> {
[route: string]: Handler<S>[];
}
class DialogBot<S> {
private waterfalls: Waterfalls<S> = {};
constructor() {
}
rule() {
rule(): Rule<S> {
return {
matcher: (input) => {},
action: (input, args) => {},
name: 'Dialog'
}
}
dialog<S>(name: string, handlers: DialogHandler<S>[]) {
next() {
}
dialog(route: string, handlers: Handler<S>[]) {
this.waterfalls[route] = handlers;
}
}
import { Observable } from 'rxjs';
import { ITextSession, Handler, Rule } from './Rules';
import { ITextInput, Action, Rule } from './Rules';
// a temporary model for LUIS built from my imagination because I was offline at the time
interface LuisMatch {
interface LuisMatcher {
intent: string,

@@ -13,7 +13,7 @@ entities: any,

interface LuisResponse {
matches: LuisMatch[];
matchers: LuisMatcher[];
}
interface LuisCache {
[message: string]: LuisMatch[];
[message: string]: LuisMatcher[];
}

@@ -35,8 +35,8 @@

export interface LuisRule<S extends ITextSession> {
export interface LuisRule<S extends ITextInput> {
intent: string,
handler: Handler<S>,
action: Action<S>,
}
export class LUIS<S extends ITextSession> {
export class LUIS<S extends ITextInput> {
private models: LuisModels = {};

@@ -103,3 +103,3 @@

// a mock because I don't really care about really calling LUIS yet
private call(modelName: string, utterance: string): Observable<LuisMatch[]> {
private call(modelName: string, utterance: string): Observable<LuisMatcher[]> {
const model = this.models[modelName];

@@ -122,6 +122,6 @@ if (!model) {

intent(intent: string, handler: Handler<S>): LuisRule<S> {
intent(intent: string, action: Action<S>): LuisRule<S> {
return {
intent,
handler
action
}

@@ -131,15 +131,15 @@ }

// "classic" LUIS usage - for a given model, say what to do with each intent above a given threshold
// IMPORTANT: the order of rules is not important - the handler for the *highest-ranked intent* will be executed
// IMPORTANT: the order of rules is not important - the action for the *highest-ranked intent* will be executed
rule(modelName: string, luisRules: LuisRule<S>[], threshold = .50): Rule<S> {
return {
recognizer: (session) =>
this.call(modelName, session.text)
matcher: (input) =>
this.call(modelName, input.text)
.flatMap(luisResult =>
Observable.from(luisResult)
.filter(match => match.threshold >= threshold)
.filter(match => luisRules.some(luisRule => luisRule.intent === match.intent))
.filter(matcher => matcher.threshold >= threshold)
.filter(matcher => luisRules.some(luisRule => luisRule.intent === matcher.intent))
.take(1) // take the highest ranked intent in our rule list
),
handler: (session, args: LuisMatch) =>
luisRules.find(luisRule => luisRule.intent === args.intent).handler(session, args.entities),
action: (input, args: LuisMatcher) =>
luisRules.find(luisRule => luisRule.intent === args.intent).action(input, args.entities),
name: `LUIS: ${modelName}/${luisRules.map(lr => lr.intent).join('+')}`

@@ -146,0 +146,0 @@ };

@@ -7,3 +7,3 @@ // Eventually this libary might be factored into multiple libraries/plugins. Until then, this

// Recognizers
// Matchers
export * from './LUIS';

@@ -18,7 +18,7 @@ export * from './RegExp';

// Session Interfaces
// Input Interfaces
export * from './State';
export * from './ChatState';
// Session Providers
// Input Providers
export * from './ReduxChat';

@@ -1,72 +0,119 @@

import { CardAction, IChatSession } from './Chat';
import { ITextSession, Handler, Recognizer, Rule, rule, filter, firstMatch } from './Rules';
import { Observable } from 'rxjs';
import { CardAction, IChatInput, Activity } from './Chat';
import { ITextInput, Action, Matcher, Result, Rule, rule, filter, firstMatcher } from './Rules';
export interface PromptRules<S extends ITextSession & IChatSession> {
[promptKey: string]: Rule<S>;
type PromptTextArgs = string;
interface PromptText<S> {
type: 'text';
text: string;
action: (input: S, args: PromptTextArgs) => Result<any>;
}
export interface PromptRulesMaker<S extends ITextSession & IChatSession> {
(prompt: Prompt<S>): PromptRules<S>;
type PromptConfirmArgs = boolean;
interface PromptConfirm<S> {
type: 'confirm';
action: (input: S, args: PromptConfirmArgs) => Result<any>;
}
export type Choice = string; // TODO: eventually this will be something more complex
type PromptChoiceArgs = string;
export type ChoiceList = Choice[];
interface PromptChoice<S> {
type: 'choice';
choices: string[]; // TODO: eventually this will become more complex
action: (input: S, args: PromptChoiceArgs) => Result<any>;
}
export interface ChoiceLists {
[choiceKey: string]: ChoiceList;
type PromptTypes<S> = PromptText<S> | PromptChoice<S> | PromptConfirm<S>;
export interface PromptStuff<S> {
matcher: Matcher<S>,
action: Action<S>,
creator: (input: S) => Result<any>;
}
export class Prompt<S extends ITextSession & IChatSession> {
private promptRules: PromptRules<S>;
interface Prompts<S extends ITextInput & IChatInput> {
[promptKey: string]: PromptStuff<S>;
}
export class Prompt<S extends ITextInput & IChatInput> {
private prompts: Prompts<S> = {};
constructor(
private choiceLists: ChoiceLists,
private promptRulesMaker: PromptRulesMaker<S>,
private getPromptKey: (session: S) => string,
private setPromptKey: (session: S, promptKey?: string) => void
private getPromptKey: (input: S) => string,
private setPromptKey: (input: S, promptKey?: string) => void
) {
this.promptRules = promptRulesMaker(this);
}
// Prompt Rule Creators
text(handler: Handler<S>): Rule<S> {
return {
recognizer: (session) => ({ text: session.text }),
handler
add(promptKey: string, promptStuff: PromptStuff<S>) {
if (this.prompts[promptKey]) {
console.warn(`Prompt key ${promptKey} already exists. Plese use a different key.`);
return;
}
this.prompts[promptKey] = promptStuff;
}
choice(choiceName: string, handler: Handler<S>) {
return {
recognizer: (session) => {
const choice = this.choiceLists[choiceName].find(choice => choice.toLowerCase() === session.text.toLowerCase());
return choice && { choice };
},
handler
}
// Prompt Rule Creators
text(promptKey: string, text: string, action: Action<S>) {
this.add(promptKey, {
matcher: (input) => input.text,
action,
creator: (input) => {
this.setPromptKey(input, promptKey);
input.reply(text);
}
});
}
confirm(handler: Handler<S>) {
return {
recognizer: (session) => {
const confirm = session.text.toLowerCase() === 'yes'; // TO DO we can do better than this
return { confirm };
},
handler
}
choice(promptKey: string, text: string, choices: string[], action: Action<S>) {
this.add(promptKey, {
matcher: (input) => choices.find(choice => choice.toLowerCase() === input.text.toLowerCase()),
action,
creator: (input) => {
this.setPromptKey(input, promptKey);
input.reply({
type: 'message',
from: { id: 'MyBot' },
text,
suggestedActions: { actions: choices.map<CardAction>(choice => ({
type: 'postBack',
title: choice,
value: choice
})) }
});
}
});
}
confirm(promptKey: string, text: string, action: Action<S>) {
this.add(promptKey, {
matcher: (input) => input.text.toLowerCase() === 'yes', // TO DO we can do better than this
action,
creator: (input) => {
this.setPromptKey(input, promptKey);
input.reply({
type: 'message',
from: { id: 'MyBot' },
text,
suggestedActions: { actions: ['Yes', 'No'].map<CardAction>(choice => ({
type: 'postBack',
title: choice,
value: choice
})) }
});
}
});
}
rule(): Rule<S> {
return filter<S>((session) => this.getPromptKey(session) !== undefined, {
recognizer: (session) => {
console.log("prompt looking for", this.getPromptKey(session))
const rule = this.promptRules[this.getPromptKey(session)];
return rule && rule.recognizer(session);
return filter<S>((input) => this.getPromptKey(input) !== undefined, {
matcher: (input) => {
console.log("prompt looking for", this.getPromptKey(input))
const rule = this.prompts[this.getPromptKey(input)];
return rule && rule.matcher(input);
},
handler: (session, args) => {
const rule = this.promptRules[this.getPromptKey(session)];
this.setPromptKey(session, undefined);
return rule.handler(session, args);
action: (input, args) => {
const action = this.prompts[this.getPromptKey(input)].action;
this.setPromptKey(input, undefined);
return action(input, args);
},

@@ -77,41 +124,5 @@ name: `PROMPT`

// Prompt Message Creators -- feels like these maybe belong in the connectors?
textCreate(session: S, promptKey: string, text: string) {
this.setPromptKey(session, promptKey);
session.reply(text);
reply(promptKey: string) {
return this.prompts[promptKey].creator;
}
choiceCreate(session: S, promptKey: string, choiceName: string, text: string) {
const choiceList = this.choiceLists[choiceName];
if (!choiceList)
return;
this.setPromptKey(session, promptKey);
session.reply({
type: 'message',
from: { id: 'RecipeBot' },
text,
suggestedActions: { actions: choiceList.map<CardAction>(choice => ({
type: 'postBack',
title: choice,
value: choice
})) }
});
}
private yorn: ChoiceList = ['Yes', 'No'];
confirmCreate(session: S, promptKey: string, text: string) {
this.setPromptKey(session, promptKey);
session.reply({
type: 'message',
from: { id: 'RecipeBot' },
text,
suggestedActions: { actions: this.yorn.map<CardAction>(choice => ({
type: 'postBack',
title: choice,
value: choice
})) }
});
}
}
import { Observable } from 'rxjs';
import { ITextSession } from './Rules';
import { UniversalChat, Message, Activity, Address, getAddress, IChatSession, IChat } from './Chat';
import { ITextInput } from './Rules';
import { UniversalChat, Message, Activity, Address, getAddress, IChatInput, IChat } from './Chat';
import { Store } from 'redux';
import { IStateSession } from './State';
import { IStateInput } from './State';
export interface ReduxSession<APP, BOTDATA> extends IStateSession<BOTDATA> {
export interface ReduxInput<APP, BOTDATA> extends IStateInput<BOTDATA> {
state: APP;

@@ -13,3 +13,3 @@ store: Store<APP>;

export class ReduxChatSession<APP, BOTDATA> implements ITextSession, IChatSession, ReduxSession<APP, BOTDATA> {
export class ReduxChatInput<APP, BOTDATA> implements ITextInput, IChatInput, ReduxInput<APP, BOTDATA> {
text: string;

@@ -41,4 +41,4 @@ address: Address;

export class ReduxChat<APP, BOTDATA> implements IChat<ReduxChatSession<APP, BOTDATA>> {
session$: Observable<ReduxChatSession<APP, BOTDATA>>;
export class ReduxChat<APP, BOTDATA> implements IChat<ReduxChatInput<APP, BOTDATA>> {
input$: Observable<ReduxChatInput<APP, BOTDATA>>;

@@ -50,6 +50,6 @@ constructor(

) {
this.session$ = chat.activity$
this.input$ = chat.activity$
.filter(activity => activity.type === 'message')
.map((message: Message) => new ReduxChatSession(message, this.chat, this.store, this.getBotData));
.map((message: Message) => new ReduxChatInput(message, this.chat, this.store, this.getBotData));
}
}
import { Observable } from 'rxjs';
import { ITextSession, Handler, Rule, arrayize } from './Rules';
import { ITextInput, Action, Rule, arrayize } from './Rules';

@@ -8,20 +8,20 @@ export interface REArgs {

export class RE<S extends ITextSession> {
export class RE<S extends ITextInput> {
constructor() {
}
// Either call as re(intent, handler) or test([intent, intent, ...], handler)
// Either call as re(intent, action) or test([intent, intent, ...], action)
rule(
intents: RegExp | RegExp[],
handler: Handler<S>
action: Action<S>
): Rule<S> {
return {
recognizer: (session) =>
matcher: (input) =>
Observable.from(arrayize(intents))
.map(regexp => regexp.exec(session.text))
.filter(groups => groups && groups[0] === session.text)
.map(regexp => regexp.exec(input.text))
.filter(groups => groups && groups[0] === input.text)
.take(1)
.map(groups => ({ groups }))
.do(args => console.log("RegEx result", args)),
handler,
action,
name: `REGEXP`

@@ -28,0 +28,0 @@ };

import { Observable } from 'rxjs';
export interface ITextSession {
text: string; // plain text for recognizers that use such things
export interface ITextInput {
text: string; // plain text for matchers that use such things
}
export interface Recognizer<S> {
(session: S): any | Observable<any>;
export type Result<T> = T | Observable<T> | Promise<T>
export interface Matcher<S> {
(input: S): Result<any>; // When we have default generics the result will be typed
}
export const always = <S>(session: S) => Observable.of(true);
export const always = <S>(input: S) => Observable.of(true);
export interface Handler<S> {
(session: S, args?: any): any | Observable<any>;
export interface Action<S> {
(input: S, args?: any): Result<any>; // When we have default generics the result will be typed
}
export interface Rule<S> {
recognizer: Recognizer<S>;
handler: Handler<S>;
matcher: Matcher<S>;
action: Action<S>;
name?: string;
}
export const rule = <S>(recognizer: Recognizer<S>, handler: Handler<S>, name?: string): Rule<S> => ({
recognizer,
handler,
export const rule = <S>(matcher: Matcher<S>, action: Action<S>, name?: string): Rule<S> => ({
matcher,
action,
name
});
export const defaultRule = <S>(handler: Handler<S>): Rule<S> => rule(always, handler);
export const defaultRule = <S>(action: Action<S>): Rule<S> => rule(always, action);
export const arrayize = <T>(stuff: T | T[]) => Array.isArray(stuff) ? stuff : [stuff];
const observize = <T>(
t: T | Observable<T>
) => {
const observize = <T>(t: Result<T>) => {
if (t instanceof Observable)

@@ -43,22 +43,22 @@ return t;

export const runRecognizer = <S>(session: S, recognizer: Recognizer<S>) =>
observize(recognizer(session))
export const doMatcher = <S>(input: S, matcher: Matcher<S>) =>
observize(matcher(input))
.filter(result => !!result)
.do(args => console.log("recognizer result", args));
.do(args => console.log("matcher result", args));
export const runHandler = <S>(session: S, args: Observable<any>, handler: Handler<S>) => {
console.log(`resolving handler`);
export const doAction = <S>(input: S, args: Observable<any>, action: Action<S>) => {
console.log(`resolving action`);
return observize(handler(session, args))
.do(result => console.log("handler result", result))
.take(1); // because handlers may emit more than one value
return observize(action(input, args))
.do(result => console.log("action result", result))
.take(1); // because actions may emit more than one value
}
export const executeRule = <S>(session: S, rule: Rule<S>) =>
runRecognizer(session, rule.recognizer)
export const executeRule = <S>(input: S, rule: Rule<S>) =>
doMatcher(input, rule.matcher)
.do(args => console.log(`rule ${rule.name} succeeded!`, args))
.flatMap(args => runHandler(session, args, rule.handler));
.flatMap(args => doAction(input, args, rule.action));
export const firstMatch = <S>(... rules: Rule<S>[]): Rule<S> => ({
recognizer: (session) =>
export const firstMatcher = <S>(... rules: Rule<S>[]): Rule<S> => ({
matcher: (input) =>
Observable.from(rules)

@@ -68,11 +68,11 @@ .switchMap((rule, index) => {

return runRecognizer(session, rule.recognizer)
return doMatcher(input, rule.matcher)
.map(args => ({index, args}));
})
.take(1), // so that we don't keep going through rules
handler: (session, args: { index: number, args: any }) => rules[args.index].handler(session, args.args),
name: `firstMatch of ${rules.length} rules`
action: (input, args: { index: number, args: any }) => rules[args.index].action(input, args.args),
name: `firstMatcher of ${rules.length} rules`
});
export const bestMatch = <S>(... rules: Rule<S>[]) => {
export const bestMatcher = <S>(... rules: Rule<S>[]) => {
// This will require the ability to score individual rules

@@ -82,3 +82,3 @@ }

export interface Query<S> {
(session: S): boolean | Observable<boolean>;
(input: S): Result<boolean>;
}

@@ -91,9 +91,9 @@

export const filter = <S>(query: Query<S>, rule: Rule<S>): Rule<S> => ({
recognizer: (session) =>
observize(query(session))
matcher: (input) =>
observize(query(input))
.filter(result => !!result)
.do(_ => console.log(""))
.flatMap(_ => runRecognizer(session, rule.recognizer)),
handler: rule.handler,
.flatMap(_ => doMatcher(input, rule.matcher)),
action: rule.action,
name: `filter rule ${rule.name}`
});

@@ -1,3 +0,3 @@

export interface IStateSession<BOTDATA> {
export interface IStateInput<BOTDATA> {
data: BOTDATA;
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc