bravey
Version:
A simple JavaScript NLP-like library to help you creating your own bot.
591 lines (559 loc) • 15.8 kB
JavaScript
var ChatterBox = function(parent, title, options) {
var isMerged = 0;
if (!options) options = {};
if (!options.root) options.root = "";
if (!options.responsiveClass)
if (options.single) {
switch (options.responsive) {
case "merged":
{
isMerged = 1;
options.responsiveClass = "merged single";
break;
}
default:
{
options.responsiveClass = "single";
}
}
}
else switch (options.responsive) {
case "force":
{
options.responsiveClass = "forceresponsive";
break;
}
case "yes":
{
options.responsiveClass = "responsive";
break;
}
default:
{
options.responsiveClass = "";
}
}
var self = this;
var recognition = 0;
var recognitionresult = "";
var recognitionbutton = 0;
var interacted = 0;
function track(data) {
if (window.ga && options.trackingId) {
if (!window._trackstarted) {
ga('create', options.trackingId);
window._trackstarted = 1;
}
ga((options.trackingName ? options.trackingName + "." : "") + "send", data);
}
}
track("pageview");
function $(node, data, parent) {
if (typeof node == "string") node = document.createElement(node);
if (data) {
if (data.set)
for (var a in data.set) node[a] = data.set[a];
if (data.css)
for (var a in data.css) node.style[a] = data.css[a];
}
if (parent) parent.appendChild(node);
return node;
}
function $sprite(data, parent) {
var node = $("div");
$(node, data, parent);
return node;
}
function request(contact, method, url, data, callback) {
var xhttp;
if (window.XMLHttpRequest) xhttp = new XMLHttpRequest();
else xhttp = new ActiveXObject("Microsoft.XMLHTTP");
if (url.substr(0, 4).toLowerCase() == "http") {
var encodedparms = "url=" + encodeURIComponent(url) + "&method=" + encodeURIComponent(method) + "&";
if (data)
if (data.header) encodedparms += "header=" + encodeURIComponent(JSON.stringify(data[a])) + "&";
encodedparms = encodedparms.substr(0, encodedparms.length - 1);
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200)
if (xhttp.responseText == "*NO*") {
contact.say("Sorry! These hosted demos are limited and it looks like I can't reach third party data right now. Download and run Bravey on your own machine and try ChatterBox bots without limits!", "error");
contact.lock(true);
} else
callback(xhttp.responseText);
}
xhttp.open("POST", options.root + "chatterbox.php", true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.setRequestHeader("Content-length", encodedparms.length);
xhttp.setRequestHeader("Connection", "close");
xhttp.send(encodedparms);
} else {
if (data)
if (data.header)
for (var i in data.header) xhttp.setRequestHeader(i, data.header[i]);
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200)
callback(xhttp.responseText);
}
xhttp.open(method, url, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send();
}
}
var container = $sprite({
set: {
className: "chatcontainer"
}
}, parent);
var contactList = $sprite({
set: {
className: "contactlist"
}
}, container);
var contactNode = $("div", {
set: {
className: "contactlistheader",
innerHTML: title
}
}, contactList);
$sprite({
set: {
className: "selectchat",
innerHTML: "Choose a contact and start chatting!"
}
}, container);
var selectedContact = 0;
var contacts = [],
selectedContact = 0,
messagequeue = [],
isloading = false,
loadqueue = [];
function stopTalk(abort) {
if (recognitionbutton) recognitionbutton.innerHTML = "TALK";
if (recognition) {
if (!abort && selectedContact) selectedContact.inputtext.value = recognitionresult;
recognitionresult = "";
recognition.abort();
recognition = 0;
recognitionbutton = 0;
}
}
function talk(hudbutton) {
stopTalk(true);
if (selectedContact && selectedContact.online && !recognition) {
recognitionbutton = hudbutton;
hudbutton.innerHTML = "GO!";
recognition = new webkitSpeechRecognition();
recognition.lang = selectedContact.language;
recognition.onresult = function(event) {
if (event && event.results && event.results.length)
for (var i = 0; i < event.results.length; i++)
if (event.results[i].isFinal) {
var conf = -1;
for (var j = 0; j < event.results[i].length; j++)
if (conf < event.results[i][j].confidence) {
conf = event.results[i][j].confidence;
recognitionresult = event.results[i][j].transcript;
}
}
}
recognition.onerror = function(event) {
stopTalk(true);
}
recognition.onend = function(event) {
stopTalk();
}
recognition.start();
}
}
function loadBot() {
if (isloading) return;
var contact = loadqueue[0];
if (contact) {
loadqueue.splice(0, 1);
if (contact._bot) {
contact._bot.loaded = 1;
contact._updateStatus();
isloading = true;
delete window.BOTLoader;
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = options.root + contact._bot.js;
script.onload = function() {
if (window.BOTLoader) window.BOTLoader(contact);
isloading = false;
loadBot();
};
head.appendChild(script);
}
}
}
function selectContact(contact) {
stopTalk(true);
self.goToChats();
selectedContact = 0;
for (var i = 0; i < contacts.length; i++)
if (contacts[i] === contact) {
if (contact._bot && !contact._bot.loaded && (loadqueue.indexOf(contact) == -1)) {
track({
hitType: "event",
eventCategory: 'Bot',
eventAction: 'requested',
eventLabel: contact.name
});
loadqueue.push(contact);
loadBot();
}
selectedContact = contact;
$(contacts[i].contactNode, {
set: {
className: "contact selected"
}
});
$(contacts[i].chatWindow, {
css: {
display: "block"
}
});
} else {
$(contacts[i].contactNode, {
set: {
className: "contact"
}
});
$(contacts[i].chatWindow, {
css: {
display: "none"
}
});
}
}
function getDocumentHeight() {
var body = document.body,
html = document.documentElement;
return Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
}
function getScrollTop() {
return document.body.scrollTop || window.pageYOffset || document.documentElement.scrollTop || 0;
}
function setScrollTop(value) {
return window.pageYOffset = document.documentElement.scrollTop = document.body.scrollTop = value;
}
this.addBot = function(js, name, avatar, language, description, settings) {
var contact = this.addContact(name, description, avatar, language, settings);
contact._bot = {
js: js,
loaded: 0
};
contact.setOnline(false);
}
this.ready = function() {
if (options.single && (contacts.length == 1))
contacts[0].select();
else if (options.runbot)
for (var i = 0; i < contacts.length; i++)
if (contacts[i].name == options.runbot) contacts[i].select();
}
this.goToContacts = function() {
container.className = "chatcontainer gotocontacts " + options.responsiveClass;
}
this.goToChats = function() {
container.className = "chatcontainer gotochats " + options.responsiveClass;
}
this.addContact = function(name, description, avatar, language, settings) {
var talkbutton;
var contactNode = $("div", {
set: {
className: "contact"
}
}, contactList);
var row1 = $("div", {
set: {
className: "row1"
}
}, contactNode);
var contactAvatar = $sprite({
set: {
className: "contactavatar"
}
}, row1);
var contactNameList = $sprite({
set: {
className: "contactnamelist"
}
}, row1);
var statusRow = $sprite({
set: {
className: "statusrow"
}
}, contactNode);
var chatWindow = $sprite({
set: {
className: "chatwindow"
}
}, container);
var head = $sprite({
set: {
className: "head"
}
}, chatWindow);
var chatAvatar = $sprite({
set: {
className: "chatavatar",
onclick: options.single ? null : function() {
interacted = true;
self.goToContacts()
}
}
}, head);
var contactNameChat = $sprite({
set: {
className: "contactnamechat"
}
}, head);
var descriptionbox = $sprite({
set: {
className: "descriptionbox"
}
}, head);
var chatBody = $sprite({
set: {
className: "chatbody"
}
}, chatWindow);
var chatTips = $("div", {
set: {
className: "chattips"
}
}, chatBody);
var foot = $sprite({
set: {
className: "foot"
}
}, chatWindow);
var inputarea = $("div", {
set: {
className: "inputarea"
}
}, foot);
var inputtext = $("input", {
set: {
type: "text",
className: "inputtext"
}
}, inputarea);
function enter() {
if (ret.online) {
interacted = true;
var text = inputtext.value;
if (text) {
track({
hitType: "event",
eventCategory: 'Bot',
eventAction: 'messaged',
eventLabel: ret.name
});
messagequeue.push({
say: 0,
html: text,
chatBody: chatBody
});
if (ret.onReceive) ret.onReceive(text);
inputtext.value = "";
}
}
}
inputtext.onkeydown = function(e) {
if (e.keyCode == 13) enter();
}
$sprite({
set: {
innerHTML: "SEND",
onclick: enter,
className: "send"
}
}, foot);
if (window.webkitSpeechRecognition)
talkbutton = $sprite({
set: {
innerHTML: "TALK",
onclick: function() {
interacted = true;
talk(talkbutton)
},
className: "talk"
}
}, foot);
function updateStatus() {
var bulletType, label, disabled, placeholder = "Write something...";
statusRow.innerHTML = "";
if (ret.locked) {
bulletType = "unavailable";
label = "unavailable";
disabled = true;
placeholder = "Sorry. Contact unavailable."
} else if (ret.online) {
if (ret.busy) {
bulletType = "busy";
label = "busy";
disabled = true;
} else {
bulletType = "online";
label = "online";
disabled = false;
}
} else if (ret._bot && !ret._bot.loaded) {
bulletType = "busy";
label = "bot idle";
disabled = true;
} else if (ret._bot && (ret._bot.loaded == 1)) {
bulletType = "busy";
label = "bot loading...";
disabled = true;
} else {
bulletType = "busy";
label = "offline";
disabled = true;
}
$("div", {
set: {
className: "bullet " + bulletType
}
}, statusRow);
$("span", {
set: {
innerHTML: label
}
}, statusRow);
$(inputtext, {
set: {
disabled: disabled,
placeholder: placeholder
}
});
if (interacted && !disabled && (selectedContact === ret)) inputtext.focus();
}
var ret = {
settings: settings,
language: language || "en-US",
name: null,
busy: false,
locked: false,
online: false,
contactNode: contactNode,
chatWindow: chatWindow,
inputtext: inputtext,
_updateStatus: updateStatus,
root: options.root,
say: function(html, type, callback) {
messagequeue.push({
say: 1,
type: type,
callback: callback,
html: html,
chatBody: chatBody
});
},
setDescription: function(description) {
descriptionbox.innerHTML = description;
},
setName: function(name) {
this.name = name;
contactNameChat.innerHTML = name;
contactNameList.innerHTML = name;
},
setAvatar: function(avatar) {
contactAvatar.style.backgroundImage = "url('" + options.root + avatar + "')";
chatAvatar.style.backgroundImage = "url('" + options.root + avatar + "')";
},
setTips: function(tips) {
chatTips.innerHTML = tips;
},
select: function() {
selectContact(this)
},
setOnline: function(online) {
this.online = online;
updateStatus();
},
setBusy: function(busy) {
this.busy = busy;
updateStatus();
},
lock: function() {
this.locked = true;
updateStatus();
},
request: function(method, url, data, callback) {
request(this, method, url, data, callback);
}
}
contactNode.onclick = function() {
interacted = true;
ret.select();
}
ret.setOnline(false);
if (description) ret.setDescription(description);
if (name) ret.setName(name);
if (avatar) ret.setAvatar(avatar);
contacts.push(ret);
return ret;
}
function checkNewMessages() {
var newmessage = messagequeue[0];
if (newmessage) {
var msg, docHeight = getDocumentHeight();
messagequeue.splice(0, 1);
if (newmessage.say) {
var sayType = "normal";
switch (newmessage.type) {
case "error":
{
sayType = "error";
break;
}
case "notify":
{
sayType = "error";
break;
}
}
msg = $("div", {
set: {
className: "messagesay " + sayType,
innerHTML: newmessage.html
}
}, newmessage.chatBody);
$sprite({
set: {
className: "messagesaytriangle " + sayType
}
}, msg);
if (newmessage.callback) newmessage.callback(msg);
} else {
msg = $("div", {
set: {
className: "message",
innerHTML: newmessage.html
}
}, newmessage.chatBody);
$sprite({
set: {
className: "messagetriangle"
}
}, msg);
}
setTimeout(function() {
msg.style.opacity = 1;
msg.style.transform = "translate(0,0)"
}, 100);
if (isMerged) {
if (interacted) setScrollTop(getScrollTop() + getDocumentHeight() - docHeight);
} else
newmessage.chatBody.scrollTop = newmessage.chatBody.scrollHeight;
}
setTimeout(checkNewMessages, 1000);
}
this.goToContacts();
checkNewMessages();
return this;
}