var ChatBot = function () { //// common vars // custom patterns and rewrites var patterns; // the bot's name var botName; // the human's name var humanName; // the html (text / image etc.) that is displayed when the bot is busy var thinkingHtml; // a selector to all inputs the human can type to var inputs; // whether to list the capabilities below the input field var inputCapabilityListing; // the example phrases that can be said (to be listed under the input field) var examplePhrases = []; // the engines to use for answering queries that are not caught by simple patterns var engines; // whether a sample conversation is running var sampleConversationRunning = false; // a callback for after a chat entry has been added var addChatEntryCallback; // list of normalize human text before check pattern var normalizer = []; // list all the predefined commands and the commands of each engine function updateCommandDescription() { var description = ''; // first explain manually defined commands and then all by all used engines var descriptions = []; var i, j; for (i = 0; i < patterns.length; i++) { if (patterns[i].description != undefined) { descriptions.push(patterns[i].description); } } for (i = 0; i < engines.length; i++) { var caps = engines[i].getCapabilities(); for (j = 0; j < caps.length; j++) { descriptions.push(caps[j]); } } examplePhrases = []; for (i = 0; i < descriptions.length; i++) { var pdesc = descriptions[i].replace(/(['"][^'"]+['"])/gi, '$1'); pdesc = pdesc.replace(/(\[[^\[\]]+\])/gi, '$1'); //console.log(pdesc); var matches = pdesc.match(/['"](.+?)['"]<\/span>/gi); //console.log(matches); if (matches != null) { //console.log(matches); for (j = 0; j < matches.length; j++) { var cleanMatch = matches[j].replace(/<\/?span[^>]*>/gi,''); examplePhrases.push(cleanMatch.replace(/['"]/gi,'')); } } description += '
' + pdesc + '
'; } var datalist = $('#chatBotCommands'); if (datalist.size() == 0) { datalist = $(''); $('body').append(datalist); } else { datalist.html(''); } for (i = 0; i < examplePhrases.length; i++) { datalist.append($('')); } //console.log(examplePhrases); $('#chatBotCommandDescription').html(description); } // type writer function playConversation(state, pauseLength) { setTimeout(function() { var newValue = ''; if ($(inputs).val() != '|') { newValue += $(inputs).val(); } newValue += state.currentInput.slice(state.start,state.start+1); $(inputs).val(newValue); state.start++; if (state.start < state.currentInput.length) { // keep typing playConversation(state, pauseLength); } else { // press enter and wait for some time and then write the next entry ChatBot.addChatEntry(state.currentInput, "human"); ChatBot.react(state.currentInput); $(inputs).val(state.currentInput); setTimeout(function() { state.conversationArrayIndex++; state.conversationArrayIndex = state.conversationArrayIndex % state.conversationArray.length; // did we cycle through the conversation array? if so, stop if (state.conversationArrayIndex == 0) { $('#chatBotConversationLoadingBar').remove(); sampleConversationRunning = false; return; } state.start = 0; $(inputs).val('|'); state.currentInput = state.conversationArray[state.conversationArrayIndex]; playConversation(state, pauseLength); }, pauseLength); var chclb = $('#chatBotConversationLoadingBar'); if (chclb.size() == 0) { chclb = $('
'); chclb.css('position','absolute'); $('body').append(chclb); } var left = $(inputs).offset().left; var top = $(inputs).offset().top + $(inputs).outerHeight() - 3; chclb.css('left',left+'px'); chclb.css('top',top+'px'); chclb.animate({ width: $(inputs).outerWidth()+'px', }, pauseLength, function() { chclb.css('width','0'); }); } }, Math.random()*120+10); } return { Engines: { // the webknox API: http://webknox.com/api webknox: function (apiKey) { // patterns that the engine can resolve var capabilities = [ "Ask for stock prices like 'stock price apple' or '[company] stock'.", "Ask for distances as in 'how far is Perth from Melbourne' or 'distance between [place1] and [place2]'.", "Want to know what the weather is like, just ask like 'weather in San Diego, California'.", "Let WebKnox tell you a joke, just say 'tell me a joke'.", "Convert units, e.g. '2.4 miles in kilometers' or '4 tablespoons to ml?'.", "Get synonyms for a word, e.g. 'synonyms for car'.", "Ask for quotes like 'quotes about [topic]' or 'quotes about love'.", "Ask for quotes from a person 'quotes by [person]' or 'quotes by aristotle'.", "Ask for recipes like 'spaghetti recipes' or 'chocolate donuts'.", "Ask for nutrient contents like 'vitamin a in 2 carrots' or 'calories is 1 cup of butter'.", "Convert ingredients like '2 cups of butter in grams'.", "If you want more results, just say 'more'.", "For more similar results say 'more like the first/second/third...'.", "Or just ask anything that comes to mind like 'Who was pope in 1499?' or 'Who directed braveheart?'.", ]; // the context id for the current conversation var contextId = Math.random() * 100000; return { react: function (query) { $.get('https://webknox-question-answering.p.mashape.com/questions/converse?mashape-key=' + apiKey + '&contextId=' + contextId + '&text=' + encodeURIComponent(query), function (data) { var content = data.answerText; if (data.media != undefined) { content += '
'; for (var i = 0; i < data.media.length; i++) { var ob = data.media[i]; content += '
' + '' + '
' + ob.title + '
' + '
' + '
Details
' + '
' + '
More like this
' + '
' + '
'; } } ChatBot.addChatEntry(content, "bot"); ChatBot.thinking(false); }); }, getCapabilities: function () { return capabilities; }, getSuggestUrl: function() { return 'https://webknox-question-answering.p.mashape.com/questions/converse/suggest?mashape-key=' + apiKey + '&query='; } } }, // the spoonacular API: http://spoonacular.com/food-api spoonacular: function (apiKey) { // patterns that the engine can resolve var capabilities = [ "Ask for recipes like 'chicken recipes' or 'spaghetti with shrimp'", "Ask for nutrient contents like 'vitamin a in 2 carrots' or 'calories is 1 cup of butter'", "Convert something with '2 cups of butter in grams'", "If you want more results, just say 'more'", "For more similar results say 'more like the first/second/third...'", "Let spoonacular tell you a joke, just say 'tell me a joke'.", "Want to learn some food trivia, just say 'food trivia'.", ]; // the context id for the current conversation var contextId = Math.random() * 100000; return { react: function (query) { $.get('https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/food/converse?mashape-key=' + apiKey + '&contextId=' + contextId + '&text=' + encodeURIComponent(query), function (data) { var content = data.answerText; if (data.media != undefined) { content += '
'; for (var i = 0; i < data.media.length; i++) { var ob = data.media[i]; content += '
' + '' + '
' + ob.title + '
' + '
' + '
Details
' + '
' + '
More like this
' + '
' + '
'; } } ChatBot.addChatEntry(content, "bot"); ChatBot.thinking(false); }); }, getCapabilities: function () { return capabilities; }, getSuggestUrl: function() { return 'https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/food/converse/suggest?mashape-key=' + apiKey + '&query='; } } }, duckduckgo: function () { // patterns that the engine can resolve var capabilities = [ "Ask what something is like 'What is DNA'?", "Ask where something is like 'Where is China'?", "Ask about a person like 'Who is Bill Gates'?", "Say a movie/person/location name like 'Braveheart' to get information about that entity", "Say a something like 'simpsons characters' to get information about that phrase", ]; return { react: function (query) { $.ajax({ type: 'GET', url: 'https://api.duckduckgo.com/?format=json&pretty=1&q=' + encodeURIComponent(query), dataType: 'jsonp' }).done(function (data) { var content = data.AbstractText; // no direct answer? tell about related topics then if (content == '' && data.RelatedTopics.length > 0) { content = '

I found multiple answers for you:

'; var media = []; for (var i = 0; i < data.RelatedTopics.length; i++) { var ob = data.RelatedTopics[i]; if (ob.Result == undefined) { continue; } if (ob.Icon.URL != '' && ob.Icon.URL.indexOf(".ico") < 0) { media.push(ob.Icon.URL); } content += '

' + ob.Result.replace("", " ") + '

'; } ///content += '' + for (i = 0; i < media.length; i++) { var m = media[i]; content += ''; } } else { if (data.Image != undefined && data.Image != '') { content += '
'; content += '
' + '' + '
' + data.Heading + '
' + '
'; } } ChatBot.addChatEntry(content, "bot"); ChatBot.thinking(false); }); }, getCapabilities: function () { return capabilities; }, getSuggestUrl: function() { return null; } } } }, init: function (options) { var settings = jQuery.extend({ // these are the defaults. botName: 'Bot', humanName: 'You', thinkingHtml: '', inputs: '', inputCapabilityListing: true, engines: [], patterns: [], normalizer: [], addChatEntryCallback: function(entryDiv, text, origin) { entryDiv.addClass('appear'); } }, options); botName = settings.botName; humanName = settings.humanName; thinkingHtml = settings.thinkingHtml; inputs = settings.inputs; inputCapabilityListing = settings.inputCapabilityListing; engines = settings.engines; patterns = settings.patterns; addChatEntryCallback = settings.addChatEntryCallback; normalizer = settings.normalizer; // update the command description updateCommandDescription(); // input capability listing? if (inputCapabilityListing) { $(inputs).attr("list", "chatBotCommands"); } // listen to inputs on the defined fields $(inputs).keyup(function (e) { if (e.keyCode == 13) { ChatBot.addChatEntry($(this).val(), "human"); ChatBot.react($(this).val()); } //console.log($(this).val()); }); }, setBotName: function (name) { botName = name; }, setHumanName: function (name) { humanName = name; $('.chatBotChatEntry.human .origin').html(name); }, addChatEntry: function addChatEntry(text, origin) { if (text == undefined) { return; } if (text == '') { text = 'Sorry, I have no idea.'; } var entryDiv = $('
'); entryDiv.html('' + (origin == 'bot' ? botName : humanName) + '' + text); $('#chatBotHistory').prepend(entryDiv); if (addChatEntryCallback != undefined) { addChatEntryCallback.call(this, entryDiv, text, origin); } }, thinking: function (on) { var ti = $('#chatBotThinkingIndicator'); if (on) { if (!sampleConversationRunning) { $(inputs).attr('disabled', 'disabled'); } ti.html(thinkingHtml); } else { if (!sampleConversationRunning) { $(inputs).removeAttr('disabled'); $(inputs).val(''); $(inputs).focus(); } ti.html(''); } }, react: function react(text) { this.thinking(true); // normalize the human text normalizer.map( method => { if( String.prototype[ method ] instanceof Function ) { // string immuable object text = text[ method ](); } else if( method instanceof Function ) { text = method( text ) || text; } } ) ; // check for custom patterns for (var i = 0; i < patterns.length; i++) { var pattern = patterns[i]; var r = new RegExp(pattern.regexp, "i"); var matches = text.match(r); //console.log(matches); if (matches) { switch (pattern.actionKey) { case 'rewrite': text = pattern.actionValue; for (var j = 1; j < matches.length; j++) { text = text.replace("$" + j, matches[j]); } //console.log("rewritten to " + text); if (pattern.callback != undefined) { pattern.callback.call(this, matches); } break; case 'response': // var response = text.replace(r, pattern.actionValue); var response = pattern.actionValue; if (response != undefined) { for (var j = 1; j < matches.length; j++) { response = response.replace("$" + j, matches[j]); } this.addChatEntry(response, "bot"); } ChatBot.thinking(false); if (pattern.callback != undefined) { pattern.callback.call(this, matches); } return; } break; } } for (var e = 0; e < engines.length; e++) { var engine = engines[e]; engine.react(text); } }, playConversation: function (conversation, pauseLength) { if (pauseLength == undefined) { pauseLength = 3000; } if (sampleConversationRunning) { return false; } $(inputs).val(''); sampleConversationRunning = true; var state = { start: 0, conversationArrayIndex: 0, conversationArray: conversation, currentInput: conversation[0] }; playConversation(state, pauseLength); return true; }, addPatternObject: function (obj) { patterns.push(obj); updateCommandDescription(); }, addPattern: function (regexp, actionKey, actionValue, callback, description) { var obj = { regexp: regexp, actionKey: actionKey, actionValue: actionValue, description: description, callback: callback }; this.addPatternObject(obj); } } }();