Skip to content

Commit cab45c8

Browse files
author
dmitrizzle
authored
Merge pull request #47 from dmitrizzle/remember-session
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.
2 parents a40b784 + b01e3cd commit cab45c8

File tree

4 files changed

+153
-51
lines changed

4 files changed

+153
-51
lines changed

component/Bubbles.js

+95-16
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ function Bubbles(container, self, options) {
66
typeSpeed = options.typeSpeed || 5 // delay per character, to simulate the machine "typing"
77
widerBy = options.widerBy || 2 // add a little extra width to bubbles to make sure they don't break
88
sidePadding = options.sidePadding || 6 // padding on both sides of chat bubbles
9+
recallInteractions = options.recallInteractions || 0 // number of interactions to be remembered and brought back upon restart
910
inputCallbackFn = options.inputCallbackFn || false // should we display an input field?
1011

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

18+
// local storage for recalling conversations upon restart
19+
var localStorageCheck = function() {
20+
var test = "chat-bubble-storage-test"
21+
try {
22+
localStorage.setItem(test, test)
23+
localStorage.removeItem(test)
24+
return true
25+
} catch (error) {
26+
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.");
27+
return false
28+
}
29+
}
30+
var localStorageAvailable = localStorageCheck() && recallInteractions > 0
31+
var interactionsLS = "chat-bubble-interactions"
32+
var interactionsHistory = localStorageAvailable &&
33+
JSON.parse(localStorage.getItem(interactionsLS)) || []
34+
35+
// prepare next save point
36+
interactionsSave = function(say, reply) {
37+
if(!localStorageAvailable) return
38+
// limit number of saves
39+
if (interactionsHistory.length > recallInteractions)
40+
interactionsHistory.shift() // removes the oldest (first) save to make space
41+
42+
// do not memorize buttons; only user input gets memorized:
43+
if (
44+
// `bubble-button` class name signals that it's a button
45+
say.includes("bubble-button") &&
46+
// if it is not of a type of textual reply
47+
reply !== "reply reply-freeform" &&
48+
// if it is not of a type of textual reply or memorized user choice
49+
reply !== "reply reply-pick"
50+
)
51+
// ...it shan't be memorized
52+
return
53+
54+
// save to memory
55+
interactionsHistory.push({ say: say, reply: reply })
56+
}
57+
58+
// commit save to localStorage
59+
interactionsSaveCommit = function() {
60+
if(!localStorageAvailable) return
61+
localStorage.setItem(interactionsLS, JSON.stringify(interactionsHistory))
62+
}
63+
1764
// set up the stage
1865
container.classList.add("bubble-container")
1966
var bubbleWrap = document.createElement("div")
@@ -78,8 +125,11 @@ function Bubbles(container, self, options) {
78125
this.reply(_convo[here])
79126
here ? (standingAnswer = here) : false
80127
}
128+
129+
var iceBreaker = false // this variable holds answer to whether this is the initative bot interaction or not
81130
this.reply = function(turn) {
82-
turn = typeof turn !== "undefined" ? turn : _convo.ice
131+
iceBreaker = typeof turn === "undefined"
132+
turn = !iceBreaker ? turn : _convo.ice
83133
questionsHTML = ""
84134
if (turn.reply !== undefined) {
85135
turn.reply.reverse()
@@ -92,6 +142,8 @@ function Bubbles(container, self, options) {
92142
self +
93143
".answer('" +
94144
el.answer +
145+
"', '" +
146+
el.question +
95147
"');this.classList.add('bubble-pick')\">" +
96148
el.question +
97149
"</span>"
@@ -106,13 +158,21 @@ function Bubbles(container, self, options) {
106158
})
107159
}
108160
// navigate "answers"
109-
this.answer = function(key) {
161+
this.answer = function(key, content) {
110162
var func = function(key) {
111163
typeof window[key] === "function" ? window[key]() : false
112164
}
113165
_convo[key] !== undefined
114166
? (this.reply(_convo[key]), (standingAnswer = key))
115167
: func(key)
168+
169+
// add re-generated user picks to the history stack
170+
if (_convo[key] !== undefined && content !== undefined) {
171+
interactionsSave(
172+
'<span class="bubble-button reply-pick">' + content + "</span>",
173+
"reply reply-pick"
174+
)
175+
}
116176
}
117177

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

148208
// create a bubble
149209
var bubbleQueue = false
150-
var addBubble = function(say, posted, reply) {
210+
var addBubble = function(say, posted, reply, live) {
211+
live = typeof live !== "undefined" ? live : true // bubbles that are not "live" are not animated and displayed differently
212+
var animationTime = live ? this.animationTime : 0
213+
var typeSpeed = live ? typeSpeed : 0
151214
reply = typeof reply !== "undefined" ? reply : ""
152215
// create bubble element
153216
var bubble = document.createElement("div")
154217
var bubbleContent = document.createElement("span")
155-
bubble.className = "bubble imagine " + reply
218+
bubble.className = "bubble imagine " + (!live ? " history " : "") + reply
156219
bubbleContent.className = "bubble-content"
157220
bubbleContent.innerHTML = say
158221
bubble.appendChild(bubbleContent)
@@ -179,8 +242,8 @@ function Bubbles(container, self, options) {
179242
})
180243
}
181244
// time, size & animate
182-
wait = animationTime * 2
183-
minTypingWait = animationTime * 6
245+
wait = live ? animationTime * 2 : 0
246+
minTypingWait = live ? animationTime * 6 : 0
184247
if (say.length * typeSpeed > animationTime && reply == "") {
185248
wait += typeSpeed * say.length
186249
wait < minTypingWait ? (wait = minTypingWait) : false
@@ -200,6 +263,11 @@ function Bubbles(container, self, options) {
200263
: bubble.style.width
201264
bubble.classList.add("say")
202265
posted()
266+
267+
// save the interaction
268+
interactionsSave(say, reply)
269+
!iceBreaker && interactionsSaveCommit() // save point
270+
203271
// animate scrolling
204272
containerHeight = container.offsetHeight
205273
scrollDifference = bubbleWrap.scrollHeight - bubbleWrap.scrollTop
@@ -218,6 +286,16 @@ function Bubbles(container, self, options) {
218286
setTimeout(scrollBubbles, animationTime / 2)
219287
}, wait + animationTime * 2)
220288
}
289+
290+
// recall previous interactions
291+
for (var i = 0; i < interactionsHistory.length; i++) {
292+
addBubble(
293+
interactionsHistory[i].say,
294+
function() {},
295+
interactionsHistory[i].reply,
296+
false
297+
)
298+
}
221299
}
222300

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

232310
// make HTML container element
233311
window[container] = document.createElement("div")
@@ -237,20 +315,21 @@ function prepHTML(options) {
237315
// style everything
238316
var appendCSS = function(file) {
239317
var link = document.createElement("link")
240-
link.href = file;
318+
link.href = file
241319
link.type = "text/css"
242320
link.rel = "stylesheet"
243321
link.media = "screen,print"
244322
document.getElementsByTagName("head")[0].appendChild(link)
245323
}
246-
appendCSS(relative_path+ "component/styles/input.css");
247-
appendCSS(relative_path + "component/styles/reply.css")
248-
appendCSS(relative_path + "component/styles/says.css")
249-
appendCSS(relative_path + "component/styles/setup.css")
250-
appendCSS(relative_path + "component/styles/typing.css")
251-
324+
appendCSS(relative_path + "component/styles/input.css")
325+
appendCSS(relative_path + "component/styles/reply.css")
326+
appendCSS(relative_path + "component/styles/says.css")
327+
appendCSS(relative_path + "component/styles/setup.css")
328+
appendCSS(relative_path + "component/styles/typing.css")
252329
}
253330

254331
// exports for es6
255-
exports.Bubbles = Bubbles
256-
exports.prepHTML = prepHTML
332+
if (typeof exports !== "undefined") {
333+
exports.Bubbles = Bubbles
334+
exports.prepHTML = prepHTML
335+
}

component/styles/reply.css

+12-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
padding: 0;
1010
max-width: 65%;
1111
}
12+
.bubble.reply.history {
13+
margin: 0 0 2px 0; /* remembered bubbles do not need to stand out via margin */
14+
}
1215
.bubble.reply.say {
13-
/*
16+
/*
1417
min-width: 350px;
1518
*/
1619
}
@@ -71,6 +74,14 @@
7174
height: auto;
7275
}
7376

77+
/* interaction recall styles */
78+
.bubble.history .bubble-content .bubble-button,
79+
.bubble.history.reply:not(.bubble-picked) .bubble-content .bubble-button:hover,
80+
.bubble.history.reply .bubble-content .bubble-button.bubble-pick {
81+
background: rgba(44, 44, 44, 0.67);
82+
cursor: default;
83+
}
84+
7485
/* input fields for bubbles */
7586
.bubble .bubble-content input {
7687
background: linear-gradient(193deg, #1faced, #5592dc 100%) !important;

component/styles/says.css

+45-33
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,57 @@
1-
21
/* style bubbles */
3-
.bubble, .bubble-typing {
4-
color: #212121;
5-
background: rgba(255, 255, 255, 0.84);
6-
padding: 8px 16px;
7-
border-radius: 5px 15px 15px 15px;
8-
font-weight: 400;
9-
text-transform: none;
10-
text-align: left;
11-
font-size: 16px;
12-
letter-spacing: .5px;
13-
margin: 0 0 2px 0;
14-
max-width: 65%;
15-
float: none;
16-
clear: both;
17-
line-height: 1.5em;
18-
word-break: break-word;
19-
transform-origin: left top;
20-
transition: all 200ms;
2+
.bubble,
3+
.bubble-typing {
4+
color: #212121;
5+
background: rgba(255, 255, 255, 0.84);
6+
padding: 8px 16px;
7+
border-radius: 5px 15px 15px 15px;
8+
font-weight: 400;
9+
text-transform: none;
10+
text-align: left;
11+
font-size: 16px;
12+
letter-spacing: .5px;
13+
margin: 0 0 2px 0;
14+
max-width: 65%;
15+
float: none;
16+
clear: both;
17+
line-height: 1.5em;
18+
word-break: break-word;
19+
transform-origin: left top;
20+
transition: all 200ms;
2121
}
2222
.bubble .bubble-content {
23-
transition: opacity 150ms;
23+
transition: opacity 150ms;
2424
}
2525
.bubble:not(.say) .bubble-content {
26-
opacity: 0;
26+
opacity: 0;
2727
}
28-
.bubble-typing.imagine, .bubble.imagine {
29-
transform: scale(0);
30-
transition: all 200ms, height 200ms 1s, padding 200ms 1s;
28+
.bubble-typing.imagine,
29+
.bubble.imagine {
30+
transform: scale(0);
31+
transition: all 200ms, height 200ms 1s, padding 200ms 1s;
3132
}
3233
.bubble.imagine {
33-
margin: 0;
34-
height: 0;
35-
padding: 0;
34+
margin: 0;
35+
height: 0;
36+
padding: 0;
3637
}
3738

3839
/* style media that's inside bubbles */
3940
.bubble .bubble-content img {
40-
width: calc(100% + 32px);
41-
margin: -8px -16px;
42-
overflow: hidden;
43-
display: block;
44-
border-radius: 5px 15px 15px 15px;
45-
}
41+
width: calc(100% + 32px);
42+
margin: -8px -16px;
43+
overflow: hidden;
44+
display: block;
45+
border-radius: 5px 15px 15px 15px;
46+
}
47+
48+
/* interaction recall styles */
49+
.bubble.history,
50+
.bubble.history .bubble-content,
51+
.bubble.history .bubble-content .bubble-button,
52+
.bubble.history .bubble-content .bubble-button:hover {
53+
transition: all 0ms !important;
54+
}
55+
.bubble.history {
56+
opacity: .25;
57+
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chat-bubble",
3-
"version": "1.2.5",
3+
"version": "1.3.0-rc.1",
44
"description": "Simple chatbot UI for Web with JSON scripting. Zero dependencies.",
55
"main": "component/Bubble.js",
66
"repository": {

0 commit comments

Comments
 (0)