Skip to content

Commit

Permalink
Merge pull request #47 from dmitrizzle/remember-session
Browse files Browse the repository at this point in the history
This is an RC for the memory function. Will keep it @next for a while so that it could be throughrally tested in production for Archie.AI (web and Chrome app) and only those who wish to try and break stuff.
  • Loading branch information
dmitrizzle authored Feb 1, 2018
2 parents a40b784 + b01e3cd commit cab45c8
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 51 deletions.
111 changes: 95 additions & 16 deletions component/Bubbles.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function Bubbles(container, self, options) {
typeSpeed = options.typeSpeed || 5 // delay per character, to simulate the machine "typing"
widerBy = options.widerBy || 2 // add a little extra width to bubbles to make sure they don't break
sidePadding = options.sidePadding || 6 // padding on both sides of chat bubbles
recallInteractions = options.recallInteractions || 0 // number of interactions to be remembered and brought back upon restart
inputCallbackFn = options.inputCallbackFn || false // should we display an input field?

var standingAnswer = "ice" // remember where to restart convo if interrupted
Expand All @@ -14,6 +15,52 @@ function Bubbles(container, self, options) {
//--> NOTE that this object is only assigned once, per session and does not change for this
// constructor name during open session.

// local storage for recalling conversations upon restart
var localStorageCheck = function() {
var test = "chat-bubble-storage-test"
try {
localStorage.setItem(test, test)
localStorage.removeItem(test)
return true
} catch (error) {
console.error("Your server does not allow storing data locally. Most likely it's because you've opened this page from your hard-drive. For testing you can disable your browser's security or start a localhost environment.");
return false
}
}
var localStorageAvailable = localStorageCheck() && recallInteractions > 0
var interactionsLS = "chat-bubble-interactions"
var interactionsHistory = localStorageAvailable &&
JSON.parse(localStorage.getItem(interactionsLS)) || []

// prepare next save point
interactionsSave = function(say, reply) {
if(!localStorageAvailable) return
// limit number of saves
if (interactionsHistory.length > recallInteractions)
interactionsHistory.shift() // removes the oldest (first) save to make space

// do not memorize buttons; only user input gets memorized:
if (
// `bubble-button` class name signals that it's a button
say.includes("bubble-button") &&
// if it is not of a type of textual reply
reply !== "reply reply-freeform" &&
// if it is not of a type of textual reply or memorized user choice
reply !== "reply reply-pick"
)
// ...it shan't be memorized
return

// save to memory
interactionsHistory.push({ say: say, reply: reply })
}

// commit save to localStorage
interactionsSaveCommit = function() {
if(!localStorageAvailable) return
localStorage.setItem(interactionsLS, JSON.stringify(interactionsHistory))
}

// set up the stage
container.classList.add("bubble-container")
var bubbleWrap = document.createElement("div")
Expand Down Expand Up @@ -78,8 +125,11 @@ function Bubbles(container, self, options) {
this.reply(_convo[here])
here ? (standingAnswer = here) : false
}

var iceBreaker = false // this variable holds answer to whether this is the initative bot interaction or not
this.reply = function(turn) {
turn = typeof turn !== "undefined" ? turn : _convo.ice
iceBreaker = typeof turn === "undefined"
turn = !iceBreaker ? turn : _convo.ice
questionsHTML = ""
if (turn.reply !== undefined) {
turn.reply.reverse()
Expand All @@ -92,6 +142,8 @@ function Bubbles(container, self, options) {
self +
".answer('" +
el.answer +
"', '" +
el.question +
"');this.classList.add('bubble-pick')\">" +
el.question +
"</span>"
Expand All @@ -106,13 +158,21 @@ function Bubbles(container, self, options) {
})
}
// navigate "answers"
this.answer = function(key) {
this.answer = function(key, content) {
var func = function(key) {
typeof window[key] === "function" ? window[key]() : false
}
_convo[key] !== undefined
? (this.reply(_convo[key]), (standingAnswer = key))
: func(key)

// add re-generated user picks to the history stack
if (_convo[key] !== undefined && content !== undefined) {
interactionsSave(
'<span class="bubble-button reply-pick">' + content + "</span>",
"reply reply-pick"
)
}
}

// api for typing bubble
Expand Down Expand Up @@ -147,12 +207,15 @@ function Bubbles(container, self, options) {

// create a bubble
var bubbleQueue = false
var addBubble = function(say, posted, reply) {
var addBubble = function(say, posted, reply, live) {
live = typeof live !== "undefined" ? live : true // bubbles that are not "live" are not animated and displayed differently
var animationTime = live ? this.animationTime : 0
var typeSpeed = live ? typeSpeed : 0
reply = typeof reply !== "undefined" ? reply : ""
// create bubble element
var bubble = document.createElement("div")
var bubbleContent = document.createElement("span")
bubble.className = "bubble imagine " + reply
bubble.className = "bubble imagine " + (!live ? " history " : "") + reply
bubbleContent.className = "bubble-content"
bubbleContent.innerHTML = say
bubble.appendChild(bubbleContent)
Expand All @@ -179,8 +242,8 @@ function Bubbles(container, self, options) {
})
}
// time, size & animate
wait = animationTime * 2
minTypingWait = animationTime * 6
wait = live ? animationTime * 2 : 0
minTypingWait = live ? animationTime * 6 : 0
if (say.length * typeSpeed > animationTime && reply == "") {
wait += typeSpeed * say.length
wait < minTypingWait ? (wait = minTypingWait) : false
Expand All @@ -200,6 +263,11 @@ function Bubbles(container, self, options) {
: bubble.style.width
bubble.classList.add("say")
posted()

// save the interaction
interactionsSave(say, reply)
!iceBreaker && interactionsSaveCommit() // save point

// animate scrolling
containerHeight = container.offsetHeight
scrollDifference = bubbleWrap.scrollHeight - bubbleWrap.scrollTop
Expand All @@ -218,6 +286,16 @@ function Bubbles(container, self, options) {
setTimeout(scrollBubbles, animationTime / 2)
}, wait + animationTime * 2)
}

// recall previous interactions
for (var i = 0; i < interactionsHistory.length; i++) {
addBubble(
interactionsHistory[i].say,
function() {},
interactionsHistory[i].reply,
false
)
}
}

// below functions are specifically for WebPack-type project that work with import()
Expand All @@ -227,7 +305,7 @@ function prepHTML(options) {
// options
var options = typeof options !== "undefined" ? options : {}
var container = options.container || "chat" // id of the container HTML element
var relative_path = options.relative_path || "./node_modules/chat-bubble/"
var relative_path = options.relative_path || "./node_modules/chat-bubble/"

// make HTML container element
window[container] = document.createElement("div")
Expand All @@ -237,20 +315,21 @@ function prepHTML(options) {
// style everything
var appendCSS = function(file) {
var link = document.createElement("link")
link.href = file;
link.href = file
link.type = "text/css"
link.rel = "stylesheet"
link.media = "screen,print"
document.getElementsByTagName("head")[0].appendChild(link)
}
appendCSS(relative_path+ "component/styles/input.css");
appendCSS(relative_path + "component/styles/reply.css")
appendCSS(relative_path + "component/styles/says.css")
appendCSS(relative_path + "component/styles/setup.css")
appendCSS(relative_path + "component/styles/typing.css")

appendCSS(relative_path + "component/styles/input.css")
appendCSS(relative_path + "component/styles/reply.css")
appendCSS(relative_path + "component/styles/says.css")
appendCSS(relative_path + "component/styles/setup.css")
appendCSS(relative_path + "component/styles/typing.css")
}

// exports for es6
exports.Bubbles = Bubbles
exports.prepHTML = prepHTML
if (typeof exports !== "undefined") {
exports.Bubbles = Bubbles
exports.prepHTML = prepHTML
}
13 changes: 12 additions & 1 deletion component/styles/reply.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
padding: 0;
max-width: 65%;
}
.bubble.reply.history {
margin: 0 0 2px 0; /* remembered bubbles do not need to stand out via margin */
}
.bubble.reply.say {
/*
/*
min-width: 350px;
*/
}
Expand Down Expand Up @@ -71,6 +74,14 @@
height: auto;
}

/* interaction recall styles */
.bubble.history .bubble-content .bubble-button,
.bubble.history.reply:not(.bubble-picked) .bubble-content .bubble-button:hover,
.bubble.history.reply .bubble-content .bubble-button.bubble-pick {
background: rgba(44, 44, 44, 0.67);
cursor: default;
}

/* input fields for bubbles */
.bubble .bubble-content input {
background: linear-gradient(193deg, #1faced, #5592dc 100%) !important;
Expand Down
78 changes: 45 additions & 33 deletions component/styles/says.css
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@

/* style bubbles */
.bubble, .bubble-typing {
color: #212121;
background: rgba(255, 255, 255, 0.84);
padding: 8px 16px;
border-radius: 5px 15px 15px 15px;
font-weight: 400;
text-transform: none;
text-align: left;
font-size: 16px;
letter-spacing: .5px;
margin: 0 0 2px 0;
max-width: 65%;
float: none;
clear: both;
line-height: 1.5em;
word-break: break-word;
transform-origin: left top;
transition: all 200ms;
.bubble,
.bubble-typing {
color: #212121;
background: rgba(255, 255, 255, 0.84);
padding: 8px 16px;
border-radius: 5px 15px 15px 15px;
font-weight: 400;
text-transform: none;
text-align: left;
font-size: 16px;
letter-spacing: .5px;
margin: 0 0 2px 0;
max-width: 65%;
float: none;
clear: both;
line-height: 1.5em;
word-break: break-word;
transform-origin: left top;
transition: all 200ms;
}
.bubble .bubble-content {
transition: opacity 150ms;
transition: opacity 150ms;
}
.bubble:not(.say) .bubble-content {
opacity: 0;
opacity: 0;
}
.bubble-typing.imagine, .bubble.imagine {
transform: scale(0);
transition: all 200ms, height 200ms 1s, padding 200ms 1s;
.bubble-typing.imagine,
.bubble.imagine {
transform: scale(0);
transition: all 200ms, height 200ms 1s, padding 200ms 1s;
}
.bubble.imagine {
margin: 0;
height: 0;
padding: 0;
margin: 0;
height: 0;
padding: 0;
}

/* style media that's inside bubbles */
.bubble .bubble-content img {
width: calc(100% + 32px);
margin: -8px -16px;
overflow: hidden;
display: block;
border-radius: 5px 15px 15px 15px;
}
width: calc(100% + 32px);
margin: -8px -16px;
overflow: hidden;
display: block;
border-radius: 5px 15px 15px 15px;
}

/* interaction recall styles */
.bubble.history,
.bubble.history .bubble-content,
.bubble.history .bubble-content .bubble-button,
.bubble.history .bubble-content .bubble-button:hover {
transition: all 0ms !important;
}
.bubble.history {
opacity: .25;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chat-bubble",
"version": "1.2.5",
"version": "1.3.0-rc.1",
"description": "Simple chatbot UI for Web with JSON scripting. Zero dependencies.",
"main": "component/Bubble.js",
"repository": {
Expand Down

0 comments on commit cab45c8

Please sign in to comment.