Skip to content

Instantly share code, notes, and snippets.

@fathermerry
Created August 15, 2016 07:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fathermerry/8111ae6f0418da884e5a4bbc2a97cd3a to your computer and use it in GitHub Desktop.
Save fathermerry/8111ae6f0418da884e5a4bbc2a97cd3a to your computer and use it in GitHub Desktop.
New Bot Code
function Database() {}
Database.prototype.fetch = function() {
return localforage.getItem('eatdrinkbot:conversation');
};
Database.prototype.addMessage = function(message) {
var $this = this;
$this.fetch()
.then(function(db) {
var log = db.log || [];
if (log.length == 20) log.shift();
log.push(message);
$this.update('log', log);
});
};
Database.prototype.update = function(key, value) {
this.fetch()
.then(function(db) {
var conversation = db;
conversation[key] = value;
localforage.setItem('eatdrinkbot:conversation', conversation);
});
};
function Conversation() {
this.db = new Database();
this._init();
}
Conversation.prototype._init = function() {
var $this = this;
$this.db.fetch()
.then(function(conversation) {
if (conversation && conversation.uid) {
return Promise.resolve(conversation);
}
$this.setupPubnub().then(function(conversation) {
conversation = conversation.json();
localforage.setItem('eatdrinkbot:conversation', conversation);
return Promise.resolve(conversation);
});
})
.then(function(conversation) {
$this.uid = conversation.uid;
if (conversation.onboarded) {
var history = conversation.log || [];
return Promise.resolve(history);
}
$this.onboard('intro');
return Promise.reject();
})
.then(function(history) {
UI.Thread.populate(history);
UI.InputField.addLiveEventListener();
UI.initialize();
return $this.start();
})
.catch(function(error) {
UI.InputField.disable({ message: error });
});
};
Conversation.prototype.setupPubnub = function() {
return fetch('http://eatdrink-api.herokuapp.com/api/start', {
method: 'post',
body: JSON.stringify({
message: 'okay'
})
});
};
Conversation.prototype.onboard = function(stage, value) {
var $this = this;
switch (stage) {
case "intro":
var messages = ["How far! I'm here to help you find the ideal restaurant in Lagos.", "What do I call you?"];
UI.initialize();
UI.Thread.play(messages)
.then(function() {
UI.InputField.addDemoEventListener('name');
});
break;
case "guide":
var messages = [];
var name = value.capitalize();
if (name.length > 15) {
name = UI.Utils.truncate(name, 15);
messages.push("That's a long one. I'll call you " + name);
messages.push("I assume this is your first time here.");
} else {
messages.push("Okay, " + name + ". I assume this is your first time here.");
}
messages.push("Frequently asked questions are on the left.");
messages.push("Your saved restaurants are listed on the right.");
messages.push("Type <b>okay</b> to start using the bot");
UI.Thread.play(messages)
.then(function() {
UI.InputField.addDemoEventListener('okay');
});
break;
case "complete":
$this.db.update('onboarded', true);
$this.start()
.then(function() {
return $this.sendMessage('okay');
})
.then(function() {
UI.InputField.addLiveEventListener();
})
.catch(function(error) {
UI.InputField.disable({ message: error });
});
break;
default:
break;
}
};
Conversation.prototype.start = function() {
var $this = this;
return new Promise(function(resolve, reject) {
UI.InputField.disable({ message: 'Connecting...' });
if (typeof PUBNUB == 'undefined') {
reject('Could not connect to streaming server. Refresh page.');
}
var pubnub = PUBNUB({
subscribe_key: 'sub-c-af12bf36-bd26-11e5-8a35-0619f8945a4f',
publish_key: 'pub-c-ecfc3624-b442-47ec-a36d-3f4d35d86e83'
});
pubnub.subscribe({
channel: $this.uid,
message: function(message, env, channel) {
$this.respond(message);
},
connect: function() {
UI.InputField.reset();
resolve();
console.log("Connected");
},
disconnect: function() {
UI.InputField.disable();
console.log("Disconnected");
},
reconnect: function() {
UI.InputField.enable();
console.log("Reconnected");
},
error: function() {
console.log("Network Error");
},
});
});
};
Conversation.prototype.sendMessage = function(message) {
var $this = this;
UI.InputField.disable();
return fetch('http://eatdrink-api.herokuapp.com/api/message', {
method: 'post',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify({
message: message,
uid: $this.uid
})
});
};
Conversation.prototype.respond = function(message) {
this.db.addMessage(message);
UI.Thread.add(message);
};
var Bot = new Conversation();
// UI Stuff
var UI = {};
UI.Elements = {
body: document.body,
thread: document.getElementById('thread'),
chatInput: document.getElementById('chat-input'),
tagInput: document.getElementById('tag-input'),
chatForm: document.getElementById('chat-form'),
chatField: document.getElementById('chat-field'),
chatWrapper: document.getElementById('chat-wrapper'),
overlay: document.getElementById('overlay'),
showTriggers: document.getElementsByClassName('show'),
hideTriggers: document.getElementsByClassName('hide')
};
UI.initialize = function() {
String.prototype.capitalize = function() {
return this.charAt(0).toUpperCase() + this.slice(1);
};
UI.Elements.body.style.display = '';
UI.addEventListeners();
};
UI.addEventListeners = function() {
UI.Elements.chatInput.addEventListener('focus', function() {
setTimeout(function() {
UI.Elements.chatWrapper.style.height = window.innerHeight + 'px';
document.body.scrollTop = document.documentElement.scrollTop = 0;
}, 300);
});
UI.Elements.chatInput.addEventListener('blur', function() {
UI.Elements.chatWrapper.style.height = '100%';
});
for (var i = 0; i < UI.Elements.showTriggers.length; i++) {
element = UI.Elements.showTriggers[i];
element.addEventListener('click', function() {
var divName = this.getAttribute('data-div');
var divToShow = document.getElementById(divName);
var animation = divToShow.getAttribute('data-animate');
UI.Elements.overlay.style.display = 'block';
if (animation) {
var animation_classes = animation.split(" ");
for (var j = 0; j < animation_classes.length; j++) {
UI.Utils.addClass(divToShow, animation_classes[j]);
}
}
divToShow.style.zIndex = '100';
divToShow.style.display = 'block';
});
}
for (var j = 0; j < UI.Elements.hideTriggers.length; j++) {
element = UI.Elements.showTriggers[j];
element.addEventListener('click', function() {
var divName = this.getAttribute('data-div');
var divToHide = document.getElementById(divName);
var animation = divToHide.getAttribute('data-animate');
UI.Elements.overlay.style.display = 'none';
if (animation) {
var animation_classes = animation.split(" ");
for (var j = 0; j < animation_classes.length; j++) {
UI.Utils.removeClass(divToHide, animation_classes[j]);
}
}
divToHide.style.zIndex = '';
divToHide.style.display = 'none';
});
}
};
UI.Utils = {
addClass: function(el, className) {
if (el.classList)
el.classList.add(className);
else
el.className += ' ' + className;
},
removeClass: function(el, className) {
if (el.classList)
el.classList.remove(className);
else
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
},
_checkIfRestart: function(message) {
if (message == 'rs' || message == 'restart') {
localforage.removeItem('eatdrinkbot:conversation');
location.reload();
return;
}
},
truncate: function(string) {
string = string.split(" ")[0];
if (string.length > length) {
string = string.substring(0, length);
}
return string;
}
};
UI.Thread = {
populate: function(log) {
UI.Elements.thread.style.opacity = '0';
log.forEach(function(message, index) {
if (message.options) delete message.options;
UI.Thread.add(message);
if (index + 1 == log.length) {
UI.Elements.thread.style.opacity = '';
}
});
},
add: function(message) {
var div;
if (message.type == 'location') {
div = UI.Thread._createLocation(message);
} else {
div = UI.Thread._createBubble(message);
}
var clear = document.createElement("div");
clear.className = "clear";
UI.Elements.thread.appendChild(div);
UI.Elements.thread.appendChild(clear);
UI.Elements.thread.scrollTop = UI.Elements.thread.offsetHeight;
if (message.options) UI.InputField.showTagInput(message);
},
_createBubble: function(message) {
var div = document.createElement("div");
div.className = message.is_app ? 'from-bot' : 'from-user';
div.className += ' animated fadeInUp';
if (message.spinner) div.className += ' sending-message';
var content = document.createElement("p");
content.innerHTML = message.body;
div.appendChild(content);
return div;
},
_createLocation: function() {
// different UI for adding location
},
removeSpinner: function() {
// find last message and remove spinner
},
play: function(messages) {
return new Promise(function(resolve, reject) {
var delay = 500;
UI.InputField.disable();
messages.forEach(function(message, index) {
var nextMessage = messages[index + 1];
setTimeout(function() {
UI.Thread.add({ is_app: true, body: message });
if (!nextMessage) {
UI.InputField.enable();
resolve();
}
}, delay);
if (nextMessage) {
var extraDelay = (nextMessage.length / 15) * 500;
delay += extraDelay;
}
});
});
}
};
UI.InputField = {
showTagInput: function(message) {
UI.Elements.chatInput.style.display = 'none';
UI.Elements.tagInput.style.display = 'block';
UI.Utils.addClass(UI.Elements.chatField, 'has-tag-input');
UI.InputField._populateTagInput(message.options);
},
hideTagInput: function() {
UI.Elements.tagInput.style.display = 'none';
UI.Elements.tagInput.innerHTML = '';
UI.Utils.removeClass(UI.Elements.chatField, 'has-tag-input');
UI.Elements.chatInput.style.display = 'block';
UI.Elements.chatInput.focus();
UI.InputField.reset();
},
_populateTagInput: function(options) {
for (var i = 0; i < options.length; i++) {
var tag = document.createElement("span");
tag.setAttribute('data-index', i);
tag.innerHTML = options[i].name;
tag = UI.InputField._addTagEventListener(tag, options[i]);
UI.Elements.tagInput.appendChild(tag);
}
},
_addTagEventListener: function(tag, option) {
tag.addEventListener('click', function() {
UI.Thread.add({ body: option.name });
UI.InputField.disable();
var index = this.getAttribute('data-index');
Bot.sendMessage(index).then(function() {
UI.InputField.hideTagInput();
});
});
return tag;
},
addLiveEventListener: function() {
UI.Elements.chatForm.addEventListener('submit', function(e) {
e.preventDefault();
var message = UI.Elements.chatInput.value;
if (!message) return;
UI.Utils._checkIfRestart(message);
UI.Thread.add({ body: message, spinner: true });
Bot.sendMessage(message).then(function() {
UI.Thread.removeSpinner();
UI.InputField.reset();
Bot.db.addMessage({ body: message });
});
});
},
addDemoEventListener: function(listener) {
switch (listener) {
case "name":
UI.Elements.chatForm.addEventListener('submit', UI.InputField._listenForName);
break;
case "okay":
UI.Elements.chatForm.removeEventListener('submit', UI.InputField._listenForName);
UI.Elements.chatForm.addEventListener('submit', UI.InputField._listenForOkay);
break;
}
},
_listenForName: function(e) {
e.preventDefault();
var name = UI.InputField._validateMessage();
if (name) Bot.onboard('guide', name);
},
_listenForOkay: function(e) {
e.preventDefault();
var message = UI.InputField._validateMessage();
if (message) {
message = message.toLowerCase();
if (message == 'ok' || message == 'okay') {
UI.Elements.chatForm.removeEventListener('submit', UI.InputField._listenForOkay);
Bot.onboard('complete');
}
}
},
_validateMessage: function() {
var message = UI.Elements.chatInput.value;
if (!message) return false;
UI.Thread.add({ body: message });
UI.InputField.reset();
return message;
},
disable: function(options) {
options = options || {};
if (options.message) UI.Elements.chatInput.value = options.message;
UI.Elements.chatInput.setAttribute('disabled', true);
UI.Utils.addClass(UI.Elements.tagInput, 'disabled');
},
enable: function() {
UI.Elements.chatInput.removeAttribute('disabled');
UI.Utils.removeClass(UI.Elements.tagInput, 'disabled');
UI.Elements.chatInput.focus();
},
reset: function() {
UI.Elements.chatInput.value = '';
UI.InputField.enable();
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment