brainerce 1.30.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/dist/bot/bootstrap.global.js +236 -51
- package/dist/bot/index.d.mts +43 -1
- package/dist/bot/index.d.ts +43 -1
- package/dist/bot/index.js +700 -146
- package/dist/bot/index.mjs +700 -146
- package/dist/index.d.mts +75 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +64 -0
- package/dist/index.mjs +64 -0
- package/package.json +1 -1
package/dist/bot/index.mjs
CHANGED
|
@@ -11,7 +11,15 @@ var CHROME = {
|
|
|
11
11
|
yourMessage: "Your message",
|
|
12
12
|
send: "Send",
|
|
13
13
|
sent: "Thanks! The team will get back to you by email.",
|
|
14
|
-
close: "Close"
|
|
14
|
+
close: "Close",
|
|
15
|
+
expand: "Expand",
|
|
16
|
+
collapse: "Collapse",
|
|
17
|
+
searching: "Searching the store\u2026",
|
|
18
|
+
addToCart: "Add to cart",
|
|
19
|
+
added: "Added",
|
|
20
|
+
view: "View",
|
|
21
|
+
chooseOptions: "View product",
|
|
22
|
+
results: "From the store"
|
|
15
23
|
},
|
|
16
24
|
he: {
|
|
17
25
|
online: "\u05DE\u05D7\u05D5\u05D1\u05E8",
|
|
@@ -22,15 +30,106 @@ var CHROME = {
|
|
|
22
30
|
yourMessage: "\u05D4\u05D4\u05D5\u05D3\u05E2\u05D4 \u05E9\u05DC\u05DB\u05DD",
|
|
23
31
|
send: "\u05E9\u05DC\u05D9\u05D7\u05D4",
|
|
24
32
|
sent: "\u05EA\u05D5\u05D3\u05D4! \u05D4\u05E6\u05D5\u05D5\u05EA \u05D9\u05D7\u05D6\u05D5\u05E8 \u05D0\u05DC\u05D9\u05DB\u05DD \u05D1\u05DE\u05D9\u05D9\u05DC.",
|
|
25
|
-
close: "\u05E1\u05D2\u05D9\u05E8\u05D4"
|
|
33
|
+
close: "\u05E1\u05D2\u05D9\u05E8\u05D4",
|
|
34
|
+
expand: "\u05D4\u05E8\u05D7\u05D1\u05D4",
|
|
35
|
+
collapse: "\u05DB\u05D9\u05D5\u05D5\u05E5",
|
|
36
|
+
searching: "\u05DE\u05D7\u05E4\u05E9 \u05D1\u05D7\u05E0\u05D5\u05EA\u2026",
|
|
37
|
+
addToCart: "\u05D4\u05D5\u05E1\u05E4\u05D4 \u05DC\u05E1\u05DC",
|
|
38
|
+
added: "\u05E0\u05D5\u05E1\u05E3",
|
|
39
|
+
view: "\u05E6\u05E4\u05D9\u05D9\u05D4",
|
|
40
|
+
chooseOptions: "\u05DC\u05E6\u05E4\u05D5\u05EA \u05D1\u05DE\u05D5\u05E6\u05E8",
|
|
41
|
+
results: "\u05DE\u05EA\u05D5\u05DA \u05D4\u05D7\u05E0\u05D5\u05EA"
|
|
26
42
|
}
|
|
27
43
|
};
|
|
44
|
+
var ICONS = {
|
|
45
|
+
chat: '<svg viewBox="0 0 24 24" fill="none"><path d="M12 3C7.03 3 3 6.58 3 11c0 2.04.86 3.9 2.28 5.32-.15 1.23-.62 2.39-1.1 3.21-.13.23.05.52.31.47 1.56-.27 3.07-.93 4.13-1.62A10.6 10.6 0 0 0 12 19c4.97 0 9-3.58 9-8s-4.03-8-9-8Z" fill="currentColor"/></svg>',
|
|
46
|
+
close: '<svg viewBox="0 0 24 24" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',
|
|
47
|
+
expand: '<svg viewBox="0 0 24 24" fill="none"><path d="M14 4h6v6M10 20H4v-6M20 4l-7 7M4 20l7-7" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
48
|
+
collapse: '<svg viewBox="0 0 24 24" fill="none"><path d="M20 10h-6V4M4 14h6v6M20 4l-6 6M4 20l6-6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
49
|
+
mail: '<svg viewBox="0 0 24 24" fill="none"><rect x="3.5" y="5.5" width="17" height="13" rx="2.5" stroke="currentColor" stroke-width="1.7"/><path d="m4.5 7.5 7.5 5.5 7.5-5.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
50
|
+
send: '<svg viewBox="0 0 24 24" fill="none"><path d="M4.4 11.2 19 4.6c.7-.3 1.4.4 1.1 1.1l-6.6 14.6c-.3.7-1.3.6-1.5-.1l-1.7-5.4a1 1 0 0 0-.6-.6l-5.4-1.7c-.7-.2-.8-1.2-.1-1.5Z" fill="currentColor"/></svg>',
|
|
51
|
+
cart: '<svg viewBox="0 0 24 24" fill="none"><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.36 17H17.5a2 2 0 0 0 1.95-1.55L21 8H6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="20.5" r="1.4" fill="currentColor"/><circle cx="17" cy="20.5" r="1.4" fill="currentColor"/></svg>',
|
|
52
|
+
check: '<svg viewBox="0 0 24 24" fill="none"><path d="m5 12.5 4.5 4.5L19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
53
|
+
arrow: '<svg viewBox="0 0 24 24" fill="none"><path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>'
|
|
54
|
+
};
|
|
55
|
+
var svgParser = typeof DOMParser !== "undefined" ? new DOMParser() : null;
|
|
56
|
+
function icon(name) {
|
|
57
|
+
const markup = (ICONS[name] ?? ICONS.chat).replace(
|
|
58
|
+
"<svg ",
|
|
59
|
+
'<svg xmlns="http://www.w3.org/2000/svg" '
|
|
60
|
+
);
|
|
61
|
+
const doc = svgParser?.parseFromString(markup, "image/svg+xml");
|
|
62
|
+
const el = doc?.documentElement;
|
|
63
|
+
if (!el || el.nodeName === "parsererror") return document.createTextNode("");
|
|
64
|
+
el.setAttribute("aria-hidden", "true");
|
|
65
|
+
el.setAttribute("class", "bb-ic");
|
|
66
|
+
return el;
|
|
67
|
+
}
|
|
28
68
|
function randomId(prefix) {
|
|
29
69
|
const bytes = new Uint8Array(16);
|
|
30
70
|
crypto.getRandomValues(bytes);
|
|
31
71
|
const b64 = btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
32
72
|
return `${prefix}${b64}`;
|
|
33
73
|
}
|
|
74
|
+
function isSafeUrl(url) {
|
|
75
|
+
return /^\/(?!\/)/.test(url) || /^https?:\/\//i.test(url);
|
|
76
|
+
}
|
|
77
|
+
var INLINE_RE = /\*\*([^*\n]+)\*\*|\[([^\]\n]+)\]\(([^)\s]+)\)/g;
|
|
78
|
+
function appendInline(parent, text) {
|
|
79
|
+
let last = 0;
|
|
80
|
+
INLINE_RE.lastIndex = 0;
|
|
81
|
+
for (let m = INLINE_RE.exec(text); m; m = INLINE_RE.exec(text)) {
|
|
82
|
+
if (m.index > last) parent.appendChild(document.createTextNode(text.slice(last, m.index)));
|
|
83
|
+
if (m[1] !== void 0) {
|
|
84
|
+
const b = document.createElement("strong");
|
|
85
|
+
b.textContent = m[1];
|
|
86
|
+
parent.appendChild(b);
|
|
87
|
+
} else if (isSafeUrl(m[3])) {
|
|
88
|
+
const a = document.createElement("a");
|
|
89
|
+
a.href = m[3];
|
|
90
|
+
a.target = "_blank";
|
|
91
|
+
a.rel = "noopener noreferrer";
|
|
92
|
+
a.textContent = m[2];
|
|
93
|
+
parent.appendChild(a);
|
|
94
|
+
} else {
|
|
95
|
+
parent.appendChild(document.createTextNode(m[2]));
|
|
96
|
+
}
|
|
97
|
+
last = m.index + m[0].length;
|
|
98
|
+
}
|
|
99
|
+
if (last < text.length) parent.appendChild(document.createTextNode(text.slice(last)));
|
|
100
|
+
}
|
|
101
|
+
function renderRich(el, text) {
|
|
102
|
+
el.replaceChildren();
|
|
103
|
+
const lines = text.split("\n");
|
|
104
|
+
let list = null;
|
|
105
|
+
let para = null;
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
const bullet = /^\s*[-•*]\s+(.*)$/.exec(line);
|
|
108
|
+
if (bullet) {
|
|
109
|
+
para = null;
|
|
110
|
+
if (!list) {
|
|
111
|
+
list = document.createElement("ul");
|
|
112
|
+
el.appendChild(list);
|
|
113
|
+
}
|
|
114
|
+
const li = document.createElement("li");
|
|
115
|
+
appendInline(li, bullet[1]);
|
|
116
|
+
list.appendChild(li);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
list = null;
|
|
120
|
+
if (!line.trim()) {
|
|
121
|
+
para = null;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!para) {
|
|
125
|
+
para = document.createElement("p");
|
|
126
|
+
el.appendChild(para);
|
|
127
|
+
} else {
|
|
128
|
+
para.appendChild(document.createElement("br"));
|
|
129
|
+
}
|
|
130
|
+
appendInline(para, line);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
34
133
|
var BrainerceBot = class _BrainerceBot {
|
|
35
134
|
constructor(options) {
|
|
36
135
|
this.settings = { enabled: false };
|
|
@@ -39,10 +138,16 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
39
138
|
this.conversationId = null;
|
|
40
139
|
this.busy = false;
|
|
41
140
|
this.opened = false;
|
|
141
|
+
this.expanded = false;
|
|
42
142
|
this.destroyed = false;
|
|
143
|
+
/** Per-turn streaming state. */
|
|
144
|
+
this.pendingText = "";
|
|
145
|
+
this.cardsRow = null;
|
|
146
|
+
this.cardIds = /* @__PURE__ */ new Set();
|
|
43
147
|
this.connectionId = options.connectionId;
|
|
44
148
|
this.baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
45
149
|
this.storageKey = `brainerce-bot:${this.connectionId}`;
|
|
150
|
+
this.onAddToCart = options.onAddToCart;
|
|
46
151
|
}
|
|
47
152
|
/** Boot the widget. Resolves to null when the bot is disabled server-side. */
|
|
48
153
|
static async mount(options) {
|
|
@@ -102,81 +207,281 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
102
207
|
}
|
|
103
208
|
}
|
|
104
209
|
// --------------------------------------------------------------------------
|
|
105
|
-
// Rendering
|
|
210
|
+
// Rendering — surface system
|
|
106
211
|
// --------------------------------------------------------------------------
|
|
212
|
+
css(accent, dir, side) {
|
|
213
|
+
const square = this.settings.bubbleShape === "square";
|
|
214
|
+
const rWindow = square ? "14px" : "22px";
|
|
215
|
+
const rBubble = square ? "8px" : "15px";
|
|
216
|
+
const rTight = "5px";
|
|
217
|
+
const mode = this.settings.displayMode ?? "floating";
|
|
218
|
+
const isRail = mode === "side_rail";
|
|
219
|
+
const isFull = mode === "full_screen";
|
|
220
|
+
return `
|
|
221
|
+
:host { all: initial; }
|
|
222
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0;
|
|
223
|
+
font-family: -apple-system, "SF Pro Text", "Segoe UI Variable Text", "Segoe UI", system-ui, "Helvetica Neue", sans-serif;
|
|
224
|
+
-webkit-font-smoothing: antialiased; }
|
|
225
|
+
button { font: inherit; cursor: pointer; background: none; border: none; color: inherit; }
|
|
226
|
+
.bb { position: fixed; bottom: 24px; ${side}: 24px; z-index: 2147483000; direction: ${dir};
|
|
227
|
+
/* product-card density scales with the window size */
|
|
228
|
+
--bb-card-w: 148px; --bb-img-h: 84px; --bb-title-fs: 11.5px; --bb-price-fs: 12.5px; --bb-btn-h: 27px; }
|
|
229
|
+
/* .big = any large surface (expanded, full-screen mode, mobile takeover) */
|
|
230
|
+
.bb.big { --bb-card-w: 216px; --bb-img-h: 132px; --bb-title-fs: 13px; --bb-price-fs: 14px; --bb-btn-h: 33px; }
|
|
231
|
+
.bb.big .bb-msgs, .bb.big .bb-chips, .bb.big .bb-esc {
|
|
232
|
+
padding-inline: max(20px, calc((100% - 1340px) / 2)); }
|
|
233
|
+
.bb.big .bb-msgs { gap: 12px; padding-top: 22px; }
|
|
234
|
+
.bb.big .bb-msg { font-size: 14px; padding: 11px 15px; }
|
|
235
|
+
.bb.big .bb-header { padding: 15px 22px; }
|
|
236
|
+
/* Big-surface composer: a full-width message box with the send inside */
|
|
237
|
+
.bb.big .bb-composer { position: relative; padding: 16px 20px 20px; }
|
|
238
|
+
.bb.big .bb-input { min-height: 116px; border-radius: 18px; background: #fff;
|
|
239
|
+
border: 1px solid #e3e5ec; padding: 16px 20px 52px; font-size: 15px;
|
|
240
|
+
overflow-y: auto; box-shadow: 0 2px 8px -2px rgba(15,18,34,.06); }
|
|
241
|
+
.bb.big .bb-send { position: absolute; bottom: 34px; inset-inline-end: 36px;
|
|
242
|
+
width: 42px; height: 42px; }
|
|
243
|
+
.bb.big .bb-send .bb-ic { width: 19px; height: 19px; }
|
|
244
|
+
/* Backdrop for the large dialog: dim + blur the storefront behind it */
|
|
245
|
+
.bb-scrim { position: fixed; inset: 0; background: rgba(15,18,34,.35);
|
|
246
|
+
-webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
|
|
247
|
+
opacity: 0; pointer-events: none; transition: opacity .25s ease; }
|
|
248
|
+
.bb.open.big .bb-scrim { opacity: 1; pointer-events: auto; }
|
|
249
|
+
|
|
250
|
+
/* ---- launcher ------------------------------------------------------ */
|
|
251
|
+
.bb-launcher {
|
|
252
|
+
width: 58px; height: 58px; position: relative; display: flex; align-items: center;
|
|
253
|
+
justify-content: center; color: #fff; background: ${accent};
|
|
254
|
+
border-radius: ${square ? "16px" : "999px"}; overflow: hidden;
|
|
255
|
+
box-shadow: 0 6px 16px -4px color-mix(in srgb, ${accent} 55%, rgba(10,12,30,.4)), 0 2px 6px rgba(10,12,30,.18);
|
|
256
|
+
transition: transform .22s cubic-bezier(.34,1.56,.64,1), box-shadow .22s ease;
|
|
257
|
+
}
|
|
258
|
+
.bb-launcher:hover { transform: scale(1.07) translateY(-1px); }
|
|
259
|
+
.bb-launcher:active { transform: scale(.96); }
|
|
260
|
+
.bb-launcher img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
|
|
261
|
+
transition: opacity .18s ease, transform .25s ease; }
|
|
262
|
+
.bb-launcher .bb-ic { width: 26px; height: 26px; position: absolute;
|
|
263
|
+
transition: opacity .18s ease, transform .25s ease; }
|
|
264
|
+
.bb-launcher .bb-l-close { opacity: 0; transform: rotate(-90deg) scale(.6); }
|
|
265
|
+
.bb.open .bb-launcher .bb-l-chat, .bb.open .bb-launcher img { opacity: 0; transform: rotate(90deg) scale(.6); }
|
|
266
|
+
.bb.open .bb-launcher img { transform: scale(1.15); }
|
|
267
|
+
.bb.open .bb-launcher .bb-l-close { opacity: 1; transform: rotate(0) scale(1); }
|
|
268
|
+
|
|
269
|
+
/* ---- window -------------------------------------------------------- */
|
|
270
|
+
.bb-window {
|
|
271
|
+
position: absolute; bottom: 74px; ${side}: 0;
|
|
272
|
+
width: 384px; max-width: calc(100vw - 32px);
|
|
273
|
+
height: min(620px, calc(100vh - 122px));
|
|
274
|
+
display: flex; flex-direction: column; background: #fff;
|
|
275
|
+
border-radius: ${rWindow}; overflow: hidden;
|
|
276
|
+
box-shadow: 0 24px 64px -16px rgba(15,18,34,.28), 0 6px 20px -6px rgba(15,18,34,.14), 0 0 0 1px rgba(15,18,34,.05);
|
|
277
|
+
opacity: 0; transform: translateY(10px) scale(.97);
|
|
278
|
+
transform-origin: bottom ${side === "left" ? "left" : "right"};
|
|
279
|
+
pointer-events: none;
|
|
280
|
+
transition: opacity .2s ease, transform .24s cubic-bezier(.22,1.2,.36,1);
|
|
281
|
+
}
|
|
282
|
+
.bb.open .bb-window { opacity: 1; transform: none; pointer-events: auto; }
|
|
283
|
+
/* Shopper-expanded = the large centered dialog (same as full-screen mode) */
|
|
284
|
+
.bb.expanded .bb-window { position: fixed;
|
|
285
|
+
inset: min(6vh, 60px) max(24px, calc((100vw - 1680px) / 2));
|
|
286
|
+
width: auto; max-width: none; height: auto; }
|
|
287
|
+
${isRail ? `.bb-window { bottom: 0; top: auto; ${side}: 0; height: calc(100vh - 98px); border-end-start-radius: ${rWindow}; }
|
|
288
|
+
.bb.expanded .bb-window { width: min(560px, calc(100vw - 40px)); }` : ""}
|
|
289
|
+
${isFull ? `.bb.open .bb-window { position: fixed;
|
|
290
|
+
inset: min(6vh, 60px) max(24px, calc((100vw - 1680px) / 2));
|
|
291
|
+
width: auto; max-width: none; height: auto; }
|
|
292
|
+
.bb.open .bb-launcher { opacity: 0; pointer-events: none; }` : ""}
|
|
293
|
+
@media (max-width: 520px) {
|
|
294
|
+
.bb { bottom: 16px; ${side}: 16px; }
|
|
295
|
+
.bb.open .bb-window { position: fixed; inset: 0; width: 100%; max-width: none; height: 100%; max-height: none; border-radius: 0; }
|
|
296
|
+
.bb.open .bb-launcher { opacity: 0; pointer-events: none; }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* ---- header -------------------------------------------------------- */
|
|
300
|
+
.bb-header { display: flex; align-items: center; gap: 11px; padding: 13px 16px;
|
|
301
|
+
background: #fff; border-bottom: 1px solid #eef0f4; flex-shrink: 0; }
|
|
302
|
+
.bb-avatar { width: 38px; height: 38px; position: relative; flex-shrink: 0;
|
|
303
|
+
border-radius: 999px; background: color-mix(in srgb, ${accent} 14%, #fff);
|
|
304
|
+
color: ${accent}; font-size: 15px; font-weight: 700;
|
|
305
|
+
display: flex; align-items: center; justify-content: center;
|
|
306
|
+
box-shadow: 0 0 0 2px #fff, 0 0 0 3.5px color-mix(in srgb, ${accent} 35%, #fff); }
|
|
307
|
+
.bb-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
|
|
308
|
+
.bb-avatar::after { content: ''; position: absolute; bottom: -1px; inset-inline-end: -1px;
|
|
309
|
+
width: 10px; height: 10px; border-radius: 999px; background: #22c55e; border: 2px solid #fff; }
|
|
310
|
+
.bb-head-main { flex: 1; min-width: 0; }
|
|
311
|
+
.bb-name { display: block; font-size: 13.5px; font-weight: 650; color: #14161f;
|
|
312
|
+
letter-spacing: -.01em; line-height: 1.25;
|
|
313
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
314
|
+
.bb-status { display: block; font-size: 11px; color: #8a8f9e; line-height: 1.3; }
|
|
315
|
+
.bb-actions { display: flex; align-items: center; gap: 2px; }
|
|
316
|
+
.bb-iconbtn { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
|
|
317
|
+
border-radius: 8px; color: #9aa0ae; transition: background .12s ease, color .12s ease; }
|
|
318
|
+
.bb-iconbtn:hover { background: #f3f4f7; color: #4d5260; }
|
|
319
|
+
.bb-iconbtn .bb-ic { width: 17px; height: 17px; }
|
|
320
|
+
|
|
321
|
+
/* ---- messages ------------------------------------------------------ */
|
|
322
|
+
.bb-msgs { flex: 1; overflow-y: auto; overscroll-behavior: contain;
|
|
323
|
+
padding: 16px 14px 10px; display: flex; flex-direction: column; gap: 10px;
|
|
324
|
+
background: linear-gradient(color-mix(in srgb, ${accent} 4%, #f7f8fa), #f7f8fa 140px); }
|
|
325
|
+
.bb-msgs::-webkit-scrollbar { width: 5px; }
|
|
326
|
+
.bb-msgs::-webkit-scrollbar-thumb { background: rgba(20,22,31,.12); border-radius: 99px; }
|
|
327
|
+
.bb-msg { max-width: 84%; padding: 9px 13px; font-size: 13px; line-height: 1.55;
|
|
328
|
+
word-break: break-word; animation: bb-in .2s ease both; unicode-bidi: plaintext; }
|
|
329
|
+
.bb-msg p + p, .bb-msg p + ul, .bb-msg ul + p { margin-top: 6px; }
|
|
330
|
+
.bb-msg ul { padding-inline-start: 18px; }
|
|
331
|
+
.bb-msg li { margin: 2px 0; }
|
|
332
|
+
.bb-msg a { color: ${accent}; font-weight: 550; text-decoration: underline; text-underline-offset: 2px; }
|
|
333
|
+
.bb-msg.bot { align-self: flex-start; background: #fff; color: #232633;
|
|
334
|
+
border: 1px solid #eceef3; border-radius: ${rBubble}; border-end-start-radius: ${rTight};
|
|
335
|
+
box-shadow: 0 1px 2px rgba(15,18,34,.04); }
|
|
336
|
+
.bb-msg.user { align-self: flex-end; background: ${accent}; color: #fff;
|
|
337
|
+
border-radius: ${rBubble}; border-end-end-radius: ${rTight};
|
|
338
|
+
box-shadow: 0 2px 6px -2px color-mix(in srgb, ${accent} 50%, rgba(10,12,30,.3)); }
|
|
339
|
+
.bb-msg.err { align-self: flex-start; background: #fef2f2; color: #b91c1c;
|
|
340
|
+
border: 1px solid #fecaca; border-radius: ${rBubble}; }
|
|
341
|
+
@keyframes bb-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
|
|
342
|
+
|
|
343
|
+
/* typing / tool status */
|
|
344
|
+
.bb-typing { align-self: flex-start; display: flex; align-items: center; gap: 8px;
|
|
345
|
+
background: #fff; border: 1px solid #eceef3; border-radius: ${rBubble};
|
|
346
|
+
border-end-start-radius: ${rTight}; padding: 10px 13px; animation: bb-in .2s ease both; }
|
|
347
|
+
.bb-typing .bb-dots { display: flex; gap: 4px; }
|
|
348
|
+
.bb-typing .bb-dots i { width: 6px; height: 6px; border-radius: 99px;
|
|
349
|
+
background: color-mix(in srgb, ${accent} 65%, #aab); animation: bb-bounce 1.2s ease-in-out infinite; }
|
|
350
|
+
.bb-typing .bb-dots i:nth-child(2) { animation-delay: .15s; }
|
|
351
|
+
.bb-typing .bb-dots i:nth-child(3) { animation-delay: .3s; }
|
|
352
|
+
.bb-typing .bb-tool { font-size: 11.5px; color: #8a8f9e; display: none; }
|
|
353
|
+
.bb-typing.searching .bb-tool { display: block; }
|
|
354
|
+
@keyframes bb-bounce { 0%, 60%, 100% { transform: none; opacity: .55; } 30% { transform: translateY(-4px); opacity: 1; } }
|
|
355
|
+
|
|
356
|
+
/* ---- product cards ------------------------------------------------- */
|
|
357
|
+
.bb-shelf { align-self: stretch; animation: bb-in .25s ease both; }
|
|
358
|
+
.bb-shelf-cap { font-size: 10.5px; font-weight: 650; letter-spacing: .07em; text-transform: uppercase;
|
|
359
|
+
color: #9aa0ae; padding: 2px 4px 6px; }
|
|
360
|
+
.bb-cards { display: flex; gap: 10px; overflow-x: auto; padding: 2px 2px 8px;
|
|
361
|
+
scroll-snap-type: x proximity; scrollbar-width: none; }
|
|
362
|
+
.bb-cards::-webkit-scrollbar { display: none; }
|
|
363
|
+
.bb-card { flex: 0 0 var(--bb-card-w); scroll-snap-align: start; background: #fff;
|
|
364
|
+
border: 1px solid #eceef3; border-radius: 12px; overflow: hidden;
|
|
365
|
+
display: flex; flex-direction: column;
|
|
366
|
+
box-shadow: 0 1px 2px rgba(15,18,34,.04);
|
|
367
|
+
transition: transform .16s ease, box-shadow .16s ease; }
|
|
368
|
+
.bb-card:hover { transform: translateY(-2px); box-shadow: 0 8px 20px -8px rgba(15,18,34,.18); }
|
|
369
|
+
.bb-card-img { display: flex; width: 100%; height: var(--bb-img-h); flex: 0 0 var(--bb-img-h); overflow: hidden; background: #f1f2f5; cursor: pointer; }
|
|
370
|
+
.bb-card-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
371
|
+
.bb-card-img .bb-ic { width: 22px; height: 22px; color: #c6c9d4; margin: auto; }
|
|
372
|
+
.bb-card-body { padding: 8px 10px 10px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
|
373
|
+
.bb-card-title { font-size: var(--bb-title-fs); font-weight: 600; color: #1c1e29; line-height: 1.35;
|
|
374
|
+
letter-spacing: -.005em; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
|
375
|
+
overflow: hidden; min-height: 2.7em; cursor: pointer; unicode-bidi: plaintext; }
|
|
376
|
+
.bb-card-title:hover { color: ${accent}; }
|
|
377
|
+
.bb-card-price { font-size: var(--bb-price-fs); font-weight: 700; color: #14161f;
|
|
378
|
+
font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
|
|
379
|
+
.bb-card-cta { display: flex; gap: 5px; margin-top: auto; }
|
|
380
|
+
.bb-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 5px;
|
|
381
|
+
height: var(--bb-btn-h); border-radius: 8px; font-size: 11px; font-weight: 650;
|
|
382
|
+
white-space: nowrap; padding: 0 6px;
|
|
383
|
+
transition: filter .12s ease, background .12s ease, color .12s ease; }
|
|
384
|
+
.bb-btn .bb-ic { width: 13px; height: 13px; }
|
|
385
|
+
.bb-btn-add { background: ${accent}; color: #fff; }
|
|
386
|
+
.bb-btn-add:hover { filter: brightness(1.08); }
|
|
387
|
+
.bb-btn-add[data-state="busy"] { opacity: .65; pointer-events: none; }
|
|
388
|
+
.bb-btn-add[data-state="done"] { background: #059669; pointer-events: none; }
|
|
389
|
+
.bb-btn-ghost { background: #fff; border: 1px solid #e3e5ec; color: #3c4150; flex: 0 0 auto; padding: 0 11px; }
|
|
390
|
+
.bb-btn-ghost:hover { background: #f6f7f9; border-color: #d5d8e1; }
|
|
391
|
+
.bb-btn-ghost.bb-wide { flex: 1; color: ${accent}; border-color: color-mix(in srgb, ${accent} 35%, #e3e5ec); }
|
|
392
|
+
.bb-btn-ghost.bb-wide:hover { background: color-mix(in srgb, ${accent} 6%, #fff); }
|
|
393
|
+
.bb-btn:disabled { opacity: .45; pointer-events: none; }
|
|
394
|
+
|
|
395
|
+
/* in-card variant picker */
|
|
396
|
+
.bb-card.picking .bb-card-cta { display: none; }
|
|
397
|
+
.bb-pick { display: flex; flex-direction: column; gap: 6px; margin-top: 4px;
|
|
398
|
+
padding-top: 8px; border-top: 1px dashed #e9ebf0; position: relative; }
|
|
399
|
+
.bb-pick-close { position: absolute; top: 6px; inset-inline-end: 0;
|
|
400
|
+
width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
|
|
401
|
+
border-radius: 6px; color: #9aa0ae; }
|
|
402
|
+
.bb-pick-close:hover { background: #f3f4f7; color: #4d5260; }
|
|
403
|
+
.bb-pick-close .bb-ic { width: 11px; height: 11px; }
|
|
404
|
+
.bb-pick-key { font-size: 9.5px; font-weight: 700; text-transform: uppercase;
|
|
405
|
+
letter-spacing: .06em; color: #9aa0ae; unicode-bidi: plaintext; }
|
|
406
|
+
.bb-pick-vals { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
407
|
+
.bb-chipv { border: 1px solid #e3e5ec; border-radius: 7px; padding: 3px 9px;
|
|
408
|
+
font-size: 11px; font-weight: 550; color: #3c4150; background: #fff;
|
|
409
|
+
transition: border-color .12s ease, background .12s ease, color .12s ease; }
|
|
410
|
+
.bb-chipv:hover { border-color: color-mix(in srgb, ${accent} 50%, #e3e5ec); }
|
|
411
|
+
.bb-chipv.sel { background: ${accent}; border-color: ${accent}; color: #fff; }
|
|
412
|
+
.bb-chipv.off { opacity: .35; pointer-events: none; }
|
|
413
|
+
.bb-pick-foot { display: flex; align-items: center; justify-content: space-between;
|
|
414
|
+
gap: 6px; margin-top: 2px; }
|
|
415
|
+
.bb-pick-foot .bb-btn { flex: 0 0 auto; padding: 0 11px; }
|
|
416
|
+
.bb-pick-price { font-size: 12.5px; font-weight: 700; color: #14161f;
|
|
417
|
+
font-variant-numeric: tabular-nums; }
|
|
418
|
+
|
|
419
|
+
/* ---- starter chips -------------------------------------------------- */
|
|
420
|
+
.bb-chips { display: flex; flex-wrap: wrap; gap: 7px; padding: 4px 14px 12px; background: #f7f8fa; }
|
|
421
|
+
.bb-chip { border: 1px solid color-mix(in srgb, ${accent} 30%, #e3e5ec);
|
|
422
|
+
color: color-mix(in srgb, ${accent} 85%, #000); background: #fff; border-radius: 999px;
|
|
423
|
+
font-size: 12px; font-weight: 550; padding: 6px 13px;
|
|
424
|
+
transition: background .12s ease, transform .12s ease; }
|
|
425
|
+
.bb-chip:hover { background: color-mix(in srgb, ${accent} 7%, #fff); transform: translateY(-1px); }
|
|
426
|
+
|
|
427
|
+
/* ---- escalation sheet ----------------------------------------------- */
|
|
428
|
+
.bb-esc { display: none; flex-direction: column; gap: 8px; padding: 12px 14px;
|
|
429
|
+
background: #fff; border-top: 1px solid #eef0f4; flex-shrink: 0; }
|
|
430
|
+
.bb-esc.open { display: flex; animation: bb-in .18s ease both; }
|
|
431
|
+
.bb-esc-title { font-size: 12px; font-weight: 650; color: #3c4150; }
|
|
432
|
+
.bb-esc input, .bb-esc textarea { border: 1px solid #e3e5ec; border-radius: 10px;
|
|
433
|
+
padding: 8px 11px; font-size: 12.5px; color: #1c1e29; outline: none; resize: none; background: #fbfbfd; }
|
|
434
|
+
.bb-esc input:focus, .bb-esc textarea:focus { border-color: color-mix(in srgb, ${accent} 55%, #e3e5ec);
|
|
435
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, ${accent} 12%, transparent); background: #fff; }
|
|
436
|
+
.bb-esc-send { height: 33px; border-radius: 10px; background: ${accent}; color: #fff;
|
|
437
|
+
font-size: 12.5px; font-weight: 650; }
|
|
438
|
+
.bb-esc-send:hover { filter: brightness(1.08); }
|
|
439
|
+
.bb-esc-note { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: #047857; font-weight: 550; }
|
|
440
|
+
.bb-esc-note .bb-ic { width: 15px; height: 15px; }
|
|
441
|
+
|
|
442
|
+
/* ---- composer ------------------------------------------------------- */
|
|
443
|
+
.bb-composer { display: flex; align-items: center; gap: 8px; padding: 11px 12px;
|
|
444
|
+
background: #fff; border-top: 1px solid #eef0f4; flex-shrink: 0; }
|
|
445
|
+
.bb-input { flex: 1; border: 1px solid transparent; outline: none; font-size: 13px; color: #1c1e29;
|
|
446
|
+
background: #f1f2f5; border-radius: 999px; padding: 9px 15px; min-width: 0;
|
|
447
|
+
resize: none; overflow-y: hidden; height: 38px; line-height: 1.45;
|
|
448
|
+
transition: border-color .12s ease, box-shadow .12s ease, background .12s ease; }
|
|
449
|
+
.bb-input::placeholder { color: #9aa0ae; }
|
|
450
|
+
.bb-input:focus { background: #fff; border-color: color-mix(in srgb, ${accent} 55%, #e3e5ec);
|
|
451
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, ${accent} 12%, transparent); }
|
|
452
|
+
.bb-send { width: 36px; height: 36px; flex-shrink: 0; display: flex; align-items: center;
|
|
453
|
+
justify-content: center; border-radius: 999px; background: ${accent}; color: #fff;
|
|
454
|
+
transition: transform .15s cubic-bezier(.34,1.56,.64,1), opacity .12s ease, filter .12s ease; }
|
|
455
|
+
.bb-send .bb-ic { width: 17px; height: 17px; ${dir === "rtl" ? "transform: scaleX(-1);" : ""} }
|
|
456
|
+
.bb-send:hover { filter: brightness(1.08); transform: scale(1.06); }
|
|
457
|
+
.bb-send:disabled { opacity: .4; pointer-events: none; }
|
|
458
|
+
`;
|
|
459
|
+
}
|
|
107
460
|
render(target) {
|
|
108
461
|
const accent = this.settings.accentColor || "#6366F1";
|
|
109
462
|
const dir = RTL_LOCALES.has(this.locale) ? "rtl" : "ltr";
|
|
110
|
-
const radius = this.settings.bubbleShape === "square" ? "8px" : "16px";
|
|
111
463
|
const side = this.settings.position === "start" ? "left" : "right";
|
|
112
464
|
const sideRtlAware = dir === "rtl" ? side === "left" ? "right" : "left" : side;
|
|
465
|
+
const name = this.settings.displayName || "Assistant";
|
|
113
466
|
this.host = document.createElement("div");
|
|
114
467
|
this.host.setAttribute("data-brainerce-bot", this.connectionId);
|
|
115
468
|
this.root = this.host.attachShadow({ mode: "open" });
|
|
116
469
|
const style = document.createElement("style");
|
|
117
|
-
style.textContent =
|
|
118
|
-
:host { all: initial; }
|
|
119
|
-
* { box-sizing: border-box; font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
120
|
-
.bb-root { position: fixed; bottom: 20px; ${sideRtlAware}: 20px; z-index: 2147483000; direction: ${dir}; }
|
|
121
|
-
.bb-launcher {
|
|
122
|
-
width: 56px; height: 56px; border: none; cursor: pointer; display: flex;
|
|
123
|
-
align-items: center; justify-content: center; color: #fff; background: ${accent};
|
|
124
|
-
border-radius: ${this.settings.bubbleShape === "square" ? "14px" : "9999px"};
|
|
125
|
-
box-shadow: 0 8px 24px rgba(0,0,0,.22); transition: transform .15s ease;
|
|
126
|
-
overflow: hidden; padding: 0;
|
|
127
|
-
}
|
|
128
|
-
.bb-launcher:hover { transform: scale(1.06); }
|
|
129
|
-
.bb-launcher img { width: 100%; height: 100%; object-fit: cover; }
|
|
130
|
-
.bb-window {
|
|
131
|
-
position: absolute; bottom: 70px; ${sideRtlAware}: 0; width: 360px; max-width: calc(100vw - 32px);
|
|
132
|
-
height: 540px; max-height: calc(100vh - 110px); display: none; flex-direction: column;
|
|
133
|
-
background: #fff; border-radius: 16px; overflow: hidden;
|
|
134
|
-
box-shadow: 0 16px 48px rgba(0,0,0,.24); border: 1px solid rgba(0,0,0,.06);
|
|
135
|
-
}
|
|
136
|
-
.bb-window.open { display: flex; }
|
|
137
|
-
.bb-header { display: flex; align-items: center; gap: 10px; padding: 12px 14px; background: ${accent}; color: #fff; }
|
|
138
|
-
.bb-avatar { width: 34px; height: 34px; border-radius: 9999px; background: rgba(255,255,255,.25);
|
|
139
|
-
display: flex; align-items: center; justify-content: center; font-weight: 600; overflow: hidden; flex-shrink: 0; }
|
|
140
|
-
.bb-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
141
|
-
.bb-head-main { flex: 1; min-width: 0; }
|
|
142
|
-
.bb-name { font-size: 14px; font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
143
|
-
.bb-status { font-size: 11px; opacity: .9; display: flex; align-items: center; gap: 5px; }
|
|
144
|
-
.bb-dot { width: 6px; height: 6px; border-radius: 9999px; background: #34d399; }
|
|
145
|
-
.bb-iconbtn { background: none; border: none; color: #fff; cursor: pointer; opacity: .85; font-size: 16px; padding: 4px; }
|
|
146
|
-
.bb-iconbtn:hover { opacity: 1; }
|
|
147
|
-
.bb-messages { flex: 1; overflow-y: auto; padding: 14px; background: #f7f7f9; display: flex; flex-direction: column; gap: 8px; }
|
|
148
|
-
.bb-msg { max-width: 80%; padding: 9px 12px; border-radius: ${radius}; font-size: 13.5px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; }
|
|
149
|
-
.bb-msg.bot { align-self: flex-start; background: #fff; border: 1px solid rgba(0,0,0,.07); border-end-start-radius: 4px; }
|
|
150
|
-
.bb-msg.user { align-self: flex-end; background: ${accent}; color: #fff; border-end-end-radius: 4px; }
|
|
151
|
-
.bb-msg.err { align-self: flex-start; background: #fef2f2; color: #b91c1c; border: 1px solid #fecaca; }
|
|
152
|
-
.bb-typing { align-self: flex-start; font-size: 11.5px; color: #6b7280; padding: 2px 4px; }
|
|
153
|
-
.bb-card { align-self: flex-start; width: 230px; background: #fff; border: 1px solid rgba(0,0,0,.08);
|
|
154
|
-
border-radius: 12px; overflow: hidden; text-decoration: none; color: inherit; display: block; }
|
|
155
|
-
.bb-card img { width: 100%; height: 120px; object-fit: cover; display: block; background: #eee; }
|
|
156
|
-
.bb-card-body { padding: 9px 11px; }
|
|
157
|
-
.bb-card-title { font-size: 13px; font-weight: 600; margin: 0 0 3px; }
|
|
158
|
-
.bb-card-price { font-size: 13px; color: ${accent}; font-weight: 600; }
|
|
159
|
-
.bb-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 14px 10px; background: #f7f7f9; }
|
|
160
|
-
.bb-chip { border: 1px solid ${accent}; color: ${accent}; background: #fff; border-radius: 9999px;
|
|
161
|
-
font-size: 12px; padding: 5px 11px; cursor: pointer; }
|
|
162
|
-
.bb-inputrow { display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid rgba(0,0,0,.07); background: #fff; }
|
|
163
|
-
.bb-input { flex: 1; border: none; outline: none; font-size: 13.5px; background: #f1f1f4; border-radius: 9999px; padding: 9px 14px; }
|
|
164
|
-
.bb-send { border: none; background: ${accent}; color: #fff; width: 36px; height: 36px; border-radius: 9999px; cursor: pointer; font-size: 15px; flex-shrink: 0; }
|
|
165
|
-
.bb-send:disabled { opacity: .5; cursor: default; }
|
|
166
|
-
.bb-esc { padding: 12px 14px; background: #fff; border-top: 1px solid rgba(0,0,0,.07); display: none; flex-direction: column; gap: 8px; }
|
|
167
|
-
.bb-esc.open { display: flex; }
|
|
168
|
-
.bb-esc input, .bb-esc textarea { border: 1px solid rgba(0,0,0,.12); border-radius: 8px; padding: 8px 10px; font-size: 13px; outline: none; resize: none; }
|
|
169
|
-
.bb-esc button { border: none; background: ${accent}; color: #fff; border-radius: 8px; padding: 8px; font-size: 13px; cursor: pointer; }
|
|
170
|
-
.bb-esc-note { font-size: 12px; color: #047857; }
|
|
171
|
-
`;
|
|
470
|
+
style.textContent = this.css(accent, dir, sideRtlAware);
|
|
172
471
|
this.root.appendChild(style);
|
|
173
472
|
const rootEl = document.createElement("div");
|
|
174
|
-
rootEl.className = "bb
|
|
473
|
+
rootEl.className = "bb";
|
|
175
474
|
this.root.appendChild(rootEl);
|
|
475
|
+
const scrim = document.createElement("div");
|
|
476
|
+
scrim.className = "bb-scrim";
|
|
477
|
+
scrim.addEventListener("click", () => {
|
|
478
|
+
if (this.expanded) this.toggleExpand();
|
|
479
|
+
else this.close();
|
|
480
|
+
});
|
|
481
|
+
rootEl.appendChild(scrim);
|
|
176
482
|
this.windowEl = document.createElement("div");
|
|
177
483
|
this.windowEl.className = "bb-window";
|
|
178
484
|
rootEl.appendChild(this.windowEl);
|
|
179
|
-
const name = this.settings.displayName || "Assistant";
|
|
180
485
|
const header = document.createElement("div");
|
|
181
486
|
header.className = "bb-header";
|
|
182
487
|
const avatar = document.createElement("span");
|
|
@@ -187,7 +492,7 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
187
492
|
img.alt = "";
|
|
188
493
|
avatar.appendChild(img);
|
|
189
494
|
} else {
|
|
190
|
-
avatar.
|
|
495
|
+
avatar.appendChild(document.createTextNode(name.charAt(0).toUpperCase()));
|
|
191
496
|
}
|
|
192
497
|
const headMain = document.createElement("span");
|
|
193
498
|
headMain.className = "bb-head-main";
|
|
@@ -196,31 +501,24 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
196
501
|
nameEl.textContent = name;
|
|
197
502
|
const statusEl = document.createElement("span");
|
|
198
503
|
statusEl.className = "bb-status";
|
|
199
|
-
|
|
200
|
-
dot.className = "bb-dot";
|
|
201
|
-
statusEl.appendChild(dot);
|
|
202
|
-
statusEl.appendChild(document.createTextNode(this.t("online")));
|
|
504
|
+
statusEl.textContent = this.t("online");
|
|
203
505
|
headMain.appendChild(nameEl);
|
|
204
506
|
headMain.appendChild(statusEl);
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
closeBtn.textContent = "\u2715";
|
|
507
|
+
const actions = document.createElement("span");
|
|
508
|
+
actions.className = "bb-actions";
|
|
509
|
+
const mode = this.settings.displayMode ?? "floating";
|
|
510
|
+
if (mode !== "full_screen" && this.settings.allowExpand !== false) {
|
|
511
|
+
this.expandBtn = this.iconButton("expand", this.t("expand"), () => this.toggleExpand());
|
|
512
|
+
actions.appendChild(this.expandBtn);
|
|
513
|
+
}
|
|
514
|
+
actions.appendChild(this.iconButton("mail", this.t("leaveMessage"), () => this.toggleEscalation()));
|
|
515
|
+
actions.appendChild(this.iconButton("close", this.t("close"), () => this.close()));
|
|
215
516
|
header.appendChild(avatar);
|
|
216
517
|
header.appendChild(headMain);
|
|
217
|
-
header.appendChild(
|
|
218
|
-
header.appendChild(closeBtn);
|
|
518
|
+
header.appendChild(actions);
|
|
219
519
|
this.windowEl.appendChild(header);
|
|
220
|
-
header.querySelector('[data-act="close"]')?.addEventListener("click", () => this.close());
|
|
221
|
-
header.querySelector('[data-act="esc"]')?.addEventListener("click", () => this.toggleEscalation());
|
|
222
520
|
this.messagesEl = document.createElement("div");
|
|
223
|
-
this.messagesEl.className = "bb-
|
|
521
|
+
this.messagesEl.className = "bb-msgs";
|
|
224
522
|
this.windowEl.appendChild(this.messagesEl);
|
|
225
523
|
this.chipsEl = document.createElement("div");
|
|
226
524
|
this.chipsEl.className = "bb-chips";
|
|
@@ -234,6 +532,9 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
234
532
|
this.windowEl.appendChild(this.chipsEl);
|
|
235
533
|
const esc = document.createElement("div");
|
|
236
534
|
esc.className = "bb-esc";
|
|
535
|
+
const escTitle = document.createElement("span");
|
|
536
|
+
escTitle.className = "bb-esc-title";
|
|
537
|
+
escTitle.textContent = this.t("leaveMessage");
|
|
237
538
|
const escEmail = document.createElement("input");
|
|
238
539
|
escEmail.type = "email";
|
|
239
540
|
escEmail.name = "email";
|
|
@@ -244,46 +545,75 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
244
545
|
escMsg.placeholder = this.t("yourMessage");
|
|
245
546
|
const escSend = document.createElement("button");
|
|
246
547
|
escSend.type = "button";
|
|
548
|
+
escSend.className = "bb-esc-send";
|
|
247
549
|
escSend.textContent = this.t("send");
|
|
550
|
+
escSend.addEventListener("click", () => this.submitEscalation(esc));
|
|
551
|
+
esc.appendChild(escTitle);
|
|
248
552
|
esc.appendChild(escEmail);
|
|
249
553
|
esc.appendChild(escMsg);
|
|
250
554
|
esc.appendChild(escSend);
|
|
251
|
-
esc.querySelector("button")?.addEventListener("click", () => this.submitEscalation(esc));
|
|
252
555
|
this.windowEl.appendChild(esc);
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
this.inputEl = document.createElement("
|
|
556
|
+
const composer = document.createElement("div");
|
|
557
|
+
composer.className = "bb-composer";
|
|
558
|
+
this.inputEl = document.createElement("textarea");
|
|
256
559
|
this.inputEl.className = "bb-input";
|
|
560
|
+
this.inputEl.rows = 1;
|
|
257
561
|
this.inputEl.placeholder = this.t("placeholder");
|
|
258
562
|
this.inputEl.addEventListener("keydown", (e) => {
|
|
259
|
-
if (e.key === "Enter"
|
|
563
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
564
|
+
e.preventDefault();
|
|
565
|
+
this.send(this.inputEl?.value ?? "");
|
|
566
|
+
}
|
|
260
567
|
});
|
|
261
|
-
|
|
262
|
-
sendBtn
|
|
263
|
-
sendBtn.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
this.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
568
|
+
this.inputEl.addEventListener("input", () => this.syncSendState());
|
|
569
|
+
this.sendBtn = document.createElement("button");
|
|
570
|
+
this.sendBtn.className = "bb-send";
|
|
571
|
+
this.sendBtn.disabled = true;
|
|
572
|
+
this.sendBtn.setAttribute("aria-label", this.t("send"));
|
|
573
|
+
this.sendBtn.appendChild(icon("send"));
|
|
574
|
+
this.sendBtn.addEventListener("click", () => this.send(this.inputEl?.value ?? ""));
|
|
575
|
+
composer.appendChild(this.inputEl);
|
|
576
|
+
composer.appendChild(this.sendBtn);
|
|
577
|
+
this.windowEl.appendChild(composer);
|
|
578
|
+
this.launcherEl = document.createElement("button");
|
|
579
|
+
this.launcherEl.className = "bb-launcher";
|
|
580
|
+
this.launcherEl.setAttribute("aria-label", name);
|
|
271
581
|
if (this.settings.avatarUrl && isSafeUrl(this.settings.avatarUrl)) {
|
|
272
582
|
const img = document.createElement("img");
|
|
273
583
|
img.src = this.settings.avatarUrl;
|
|
274
584
|
img.alt = "";
|
|
275
|
-
|
|
585
|
+
this.launcherEl.appendChild(img);
|
|
276
586
|
} else {
|
|
277
|
-
|
|
587
|
+
const chatIc = icon("chat");
|
|
588
|
+
chatIc.classList.add("bb-l-chat");
|
|
589
|
+
this.launcherEl.appendChild(chatIc);
|
|
278
590
|
}
|
|
279
|
-
|
|
280
|
-
|
|
591
|
+
const closeIc = icon("close");
|
|
592
|
+
closeIc.classList.add("bb-l-close");
|
|
593
|
+
this.launcherEl.appendChild(closeIc);
|
|
594
|
+
this.launcherEl.addEventListener("click", () => this.opened ? this.close() : this.open());
|
|
595
|
+
rootEl.appendChild(this.launcherEl);
|
|
281
596
|
target.appendChild(this.host);
|
|
282
597
|
}
|
|
598
|
+
iconButton(name, label, onClick) {
|
|
599
|
+
const btn = document.createElement("button");
|
|
600
|
+
btn.className = "bb-iconbtn";
|
|
601
|
+
btn.title = label;
|
|
602
|
+
btn.setAttribute("aria-label", label);
|
|
603
|
+
btn.appendChild(icon(name));
|
|
604
|
+
btn.addEventListener("click", onClick);
|
|
605
|
+
return btn;
|
|
606
|
+
}
|
|
607
|
+
syncSendState() {
|
|
608
|
+
if (this.sendBtn) this.sendBtn.disabled = !(this.inputEl?.value ?? "").trim() || this.busy;
|
|
609
|
+
}
|
|
283
610
|
open() {
|
|
284
611
|
if (!this.windowEl || this.opened) return;
|
|
285
612
|
this.opened = true;
|
|
286
|
-
this.
|
|
613
|
+
const el = this.root?.querySelector(".bb");
|
|
614
|
+
el?.classList.add("open");
|
|
615
|
+
const takeover = (this.settings.displayMode ?? "floating") === "full_screen" || window.innerWidth <= 520;
|
|
616
|
+
el?.classList.toggle("big", takeover || this.expanded);
|
|
287
617
|
if (this.messagesEl && this.messagesEl.childElementCount === 0) {
|
|
288
618
|
void this.primeThread();
|
|
289
619
|
}
|
|
@@ -291,7 +621,20 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
291
621
|
}
|
|
292
622
|
close() {
|
|
293
623
|
this.opened = false;
|
|
294
|
-
this.
|
|
624
|
+
const el = this.root?.querySelector(".bb");
|
|
625
|
+
el?.classList.remove("open");
|
|
626
|
+
if (!this.expanded) el?.classList.remove("big");
|
|
627
|
+
}
|
|
628
|
+
toggleExpand() {
|
|
629
|
+
this.expanded = !this.expanded;
|
|
630
|
+
this.root?.querySelector(".bb")?.classList.toggle("expanded", this.expanded);
|
|
631
|
+
this.root?.querySelector(".bb")?.classList.toggle("big", this.expanded);
|
|
632
|
+
if (this.expandBtn) {
|
|
633
|
+
this.expandBtn.replaceChildren(icon(this.expanded ? "collapse" : "expand"));
|
|
634
|
+
const label = this.t(this.expanded ? "collapse" : "expand");
|
|
635
|
+
this.expandBtn.title = label;
|
|
636
|
+
this.expandBtn.setAttribute("aria-label", label);
|
|
637
|
+
}
|
|
295
638
|
}
|
|
296
639
|
/** First open: restore the server thread, or show the greeting. */
|
|
297
640
|
async primeThread() {
|
|
@@ -304,7 +647,8 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
304
647
|
if (res.ok) {
|
|
305
648
|
const data = await res.json();
|
|
306
649
|
for (const m of data.data) {
|
|
307
|
-
this.appendMessage(m.role === "assistant" ? "bot" : "user",
|
|
650
|
+
const el = this.appendMessage(m.role === "assistant" ? "bot" : "user", "");
|
|
651
|
+
renderRich(el, m.content);
|
|
308
652
|
}
|
|
309
653
|
if (data.data.length > 0) {
|
|
310
654
|
this.chipsEl?.remove();
|
|
@@ -318,7 +662,10 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
318
662
|
} catch {
|
|
319
663
|
}
|
|
320
664
|
}
|
|
321
|
-
if (this.settings.greeting)
|
|
665
|
+
if (this.settings.greeting) {
|
|
666
|
+
const el = this.appendMessage("bot", "");
|
|
667
|
+
renderRich(el, this.settings.greeting);
|
|
668
|
+
}
|
|
322
669
|
}
|
|
323
670
|
// --------------------------------------------------------------------------
|
|
324
671
|
// The chat turn
|
|
@@ -328,9 +675,13 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
328
675
|
if (!message || this.busy) return;
|
|
329
676
|
this.busy = true;
|
|
330
677
|
if (this.inputEl) this.inputEl.value = "";
|
|
678
|
+
this.syncSendState();
|
|
331
679
|
this.chipsEl?.remove();
|
|
332
680
|
this.appendMessage("user", message);
|
|
333
681
|
const typing = this.appendTyping();
|
|
682
|
+
this.pendingText = "";
|
|
683
|
+
this.cardsRow = null;
|
|
684
|
+
this.cardIds = /* @__PURE__ */ new Set();
|
|
334
685
|
let botBubble = null;
|
|
335
686
|
try {
|
|
336
687
|
const res = await fetch(
|
|
@@ -371,11 +722,13 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
371
722
|
botBubble = this.handleFrame(frame, typing, botBubble);
|
|
372
723
|
}
|
|
373
724
|
}
|
|
725
|
+
if (botBubble && this.pendingText) renderRich(botBubble, this.pendingText);
|
|
374
726
|
} catch {
|
|
375
727
|
this.appendMessage("err", this.t("error"));
|
|
376
728
|
} finally {
|
|
377
729
|
typing.remove();
|
|
378
730
|
this.busy = false;
|
|
731
|
+
this.syncSendState();
|
|
379
732
|
}
|
|
380
733
|
}
|
|
381
734
|
handleFrame(frame, typing, botBubble) {
|
|
@@ -390,12 +743,13 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
390
743
|
typing.remove();
|
|
391
744
|
botBubble = this.appendMessage("bot", "");
|
|
392
745
|
}
|
|
393
|
-
|
|
746
|
+
this.pendingText += frame.text;
|
|
747
|
+
botBubble.textContent = this.pendingText;
|
|
394
748
|
this.scrollDown();
|
|
395
749
|
return botBubble;
|
|
396
750
|
}
|
|
397
751
|
case "tool":
|
|
398
|
-
typing.
|
|
752
|
+
typing.classList.toggle("searching", frame.status === "running");
|
|
399
753
|
return botBubble;
|
|
400
754
|
case "card":
|
|
401
755
|
this.appendCard(frame.card);
|
|
@@ -409,7 +763,235 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
409
763
|
}
|
|
410
764
|
}
|
|
411
765
|
// --------------------------------------------------------------------------
|
|
412
|
-
//
|
|
766
|
+
// Product cards
|
|
767
|
+
// --------------------------------------------------------------------------
|
|
768
|
+
appendCard(card) {
|
|
769
|
+
if (!this.messagesEl) return;
|
|
770
|
+
if (this.cardIds.has(card.productId)) return;
|
|
771
|
+
this.cardIds.add(card.productId);
|
|
772
|
+
if (!this.cardsRow) {
|
|
773
|
+
const shelf = document.createElement("div");
|
|
774
|
+
shelf.className = "bb-shelf";
|
|
775
|
+
const cap = document.createElement("div");
|
|
776
|
+
cap.className = "bb-shelf-cap";
|
|
777
|
+
cap.textContent = this.t("results");
|
|
778
|
+
this.cardsRow = document.createElement("div");
|
|
779
|
+
this.cardsRow.className = "bb-cards";
|
|
780
|
+
shelf.appendChild(cap);
|
|
781
|
+
shelf.appendChild(this.cardsRow);
|
|
782
|
+
this.messagesEl.appendChild(shelf);
|
|
783
|
+
}
|
|
784
|
+
const safeHref = isSafeUrl(card.url) ? card.url : null;
|
|
785
|
+
const el = document.createElement("div");
|
|
786
|
+
el.className = "bb-card";
|
|
787
|
+
const goToProduct = () => {
|
|
788
|
+
this.beacon(card.botRef);
|
|
789
|
+
if (safeHref) window.location.href = safeHref;
|
|
790
|
+
};
|
|
791
|
+
const imgWrap = document.createElement("span");
|
|
792
|
+
imgWrap.className = "bb-card-img";
|
|
793
|
+
if (card.imageUrl && isSafeUrl(card.imageUrl)) {
|
|
794
|
+
const img = document.createElement("img");
|
|
795
|
+
img.src = card.imageUrl;
|
|
796
|
+
img.alt = "";
|
|
797
|
+
img.loading = "lazy";
|
|
798
|
+
imgWrap.appendChild(img);
|
|
799
|
+
} else {
|
|
800
|
+
imgWrap.appendChild(icon("cart"));
|
|
801
|
+
}
|
|
802
|
+
imgWrap.addEventListener("click", goToProduct);
|
|
803
|
+
el.appendChild(imgWrap);
|
|
804
|
+
const body = document.createElement("span");
|
|
805
|
+
body.className = "bb-card-body";
|
|
806
|
+
const title = document.createElement("span");
|
|
807
|
+
title.className = "bb-card-title";
|
|
808
|
+
title.textContent = card.title;
|
|
809
|
+
title.addEventListener("click", goToProduct);
|
|
810
|
+
const priceEl = document.createElement("span");
|
|
811
|
+
priceEl.className = "bb-card-price";
|
|
812
|
+
priceEl.textContent = card.price.formatted;
|
|
813
|
+
body.appendChild(title);
|
|
814
|
+
body.appendChild(priceEl);
|
|
815
|
+
const cta = document.createElement("span");
|
|
816
|
+
cta.className = "bb-card-cta";
|
|
817
|
+
if (card.requiresOptions) {
|
|
818
|
+
const cartBtn = document.createElement("button");
|
|
819
|
+
cartBtn.className = "bb-btn bb-btn-add";
|
|
820
|
+
cartBtn.setAttribute("aria-label", this.t("addToCart"));
|
|
821
|
+
cartBtn.appendChild(icon("cart"));
|
|
822
|
+
if (card.variants?.length) {
|
|
823
|
+
cartBtn.addEventListener("click", () => this.togglePicker(body, card, imgWrap));
|
|
824
|
+
} else {
|
|
825
|
+
cartBtn.addEventListener("click", goToProduct);
|
|
826
|
+
}
|
|
827
|
+
cta.appendChild(cartBtn);
|
|
828
|
+
} else {
|
|
829
|
+
const addBtn = document.createElement("button");
|
|
830
|
+
addBtn.className = "bb-btn bb-btn-add";
|
|
831
|
+
addBtn.appendChild(icon("cart"));
|
|
832
|
+
addBtn.appendChild(document.createTextNode(this.t("addToCart")));
|
|
833
|
+
addBtn.addEventListener("click", () => void this.addToCart(card, addBtn));
|
|
834
|
+
const viewBtn = document.createElement("button");
|
|
835
|
+
viewBtn.className = "bb-btn bb-btn-ghost";
|
|
836
|
+
viewBtn.appendChild(document.createTextNode(this.t("view")));
|
|
837
|
+
viewBtn.addEventListener("click", goToProduct);
|
|
838
|
+
cta.appendChild(addBtn);
|
|
839
|
+
cta.appendChild(viewBtn);
|
|
840
|
+
}
|
|
841
|
+
body.appendChild(cta);
|
|
842
|
+
el.appendChild(body);
|
|
843
|
+
this.cardsRow.appendChild(el);
|
|
844
|
+
this.scrollDown();
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* In-card variant picker: one chip-row per attribute; a complete selection
|
|
848
|
+
* resolves to a variantId and becomes a real add-to-cart. Variant image and
|
|
849
|
+
* price update live. Built entirely with createElement (no innerHTML).
|
|
850
|
+
*/
|
|
851
|
+
togglePicker(body, card, imgWrap) {
|
|
852
|
+
const cardEl = body.parentElement;
|
|
853
|
+
const existing = body.querySelector(".bb-pick");
|
|
854
|
+
if (existing) {
|
|
855
|
+
existing.remove();
|
|
856
|
+
cardEl?.classList.remove("picking");
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
cardEl?.classList.add("picking");
|
|
860
|
+
const variants = card.variants ?? [];
|
|
861
|
+
const keys = [];
|
|
862
|
+
for (const v of variants) {
|
|
863
|
+
for (const k of Object.keys(v.attributes)) if (!keys.includes(k)) keys.push(k);
|
|
864
|
+
}
|
|
865
|
+
const sel = {};
|
|
866
|
+
const pick = document.createElement("span");
|
|
867
|
+
pick.className = "bb-pick";
|
|
868
|
+
const resolved = () => variants.find((v) => keys.every((k) => sel[k] && v.attributes[k] === sel[k])) ?? null;
|
|
869
|
+
const render = () => {
|
|
870
|
+
pick.replaceChildren();
|
|
871
|
+
for (const key of keys) {
|
|
872
|
+
const label = document.createElement("span");
|
|
873
|
+
label.className = "bb-pick-key";
|
|
874
|
+
label.textContent = key;
|
|
875
|
+
const vals = document.createElement("span");
|
|
876
|
+
vals.className = "bb-pick-vals";
|
|
877
|
+
const seen = /* @__PURE__ */ new Set();
|
|
878
|
+
for (const v of variants) {
|
|
879
|
+
const val = v.attributes[key];
|
|
880
|
+
if (!val || seen.has(val)) continue;
|
|
881
|
+
seen.add(val);
|
|
882
|
+
const possible = variants.some(
|
|
883
|
+
(x) => x.attributes[key] === val && keys.every((k) => k === key || !sel[k] || x.attributes[k] === sel[k])
|
|
884
|
+
);
|
|
885
|
+
const chip = document.createElement("button");
|
|
886
|
+
chip.className = `bb-chipv${sel[key] === val ? " sel" : ""}${possible ? "" : " off"}`;
|
|
887
|
+
chip.textContent = val;
|
|
888
|
+
chip.addEventListener("click", () => {
|
|
889
|
+
if (sel[key] === val) delete sel[key];
|
|
890
|
+
else sel[key] = val;
|
|
891
|
+
render();
|
|
892
|
+
});
|
|
893
|
+
vals.appendChild(chip);
|
|
894
|
+
}
|
|
895
|
+
pick.appendChild(label);
|
|
896
|
+
pick.appendChild(vals);
|
|
897
|
+
}
|
|
898
|
+
const closeBtn = document.createElement("button");
|
|
899
|
+
closeBtn.className = "bb-pick-close";
|
|
900
|
+
closeBtn.setAttribute("aria-label", this.t("close"));
|
|
901
|
+
closeBtn.appendChild(icon("close"));
|
|
902
|
+
closeBtn.addEventListener("click", () => {
|
|
903
|
+
pick.remove();
|
|
904
|
+
cardEl?.classList.remove("picking");
|
|
905
|
+
});
|
|
906
|
+
pick.appendChild(closeBtn);
|
|
907
|
+
const match = resolved();
|
|
908
|
+
const foot = document.createElement("span");
|
|
909
|
+
foot.className = "bb-pick-foot";
|
|
910
|
+
const priceEl = document.createElement("span");
|
|
911
|
+
priceEl.className = "bb-pick-price";
|
|
912
|
+
priceEl.textContent = match ? match.price.formatted : "";
|
|
913
|
+
const addBtn = document.createElement("button");
|
|
914
|
+
addBtn.className = "bb-btn bb-btn-add";
|
|
915
|
+
addBtn.appendChild(icon("cart"));
|
|
916
|
+
addBtn.appendChild(document.createTextNode(this.t("addToCart")));
|
|
917
|
+
if (!match) addBtn.disabled = true;
|
|
918
|
+
addBtn.addEventListener("click", () => {
|
|
919
|
+
const m = resolved();
|
|
920
|
+
if (m) void this.addToCart(card, addBtn, m.id);
|
|
921
|
+
});
|
|
922
|
+
foot.appendChild(priceEl);
|
|
923
|
+
foot.appendChild(addBtn);
|
|
924
|
+
pick.appendChild(foot);
|
|
925
|
+
if (match?.imageUrl && isSafeUrl(match.imageUrl)) {
|
|
926
|
+
const img = imgWrap.querySelector("img");
|
|
927
|
+
if (img) img.src = match.imageUrl;
|
|
928
|
+
}
|
|
929
|
+
this.scrollDown();
|
|
930
|
+
};
|
|
931
|
+
render();
|
|
932
|
+
body.appendChild(pick);
|
|
933
|
+
this.scrollDown();
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Add-to-cart resolution chain — must never be a dead button:
|
|
937
|
+
* 1. host `onAddToCart` option (scaffolded stores: syncs their cart UI)
|
|
938
|
+
* 2. cancelable `brainerce:bot:add-to-cart` CustomEvent (custom embeds)
|
|
939
|
+
* 3. fallback: navigate to the product page
|
|
940
|
+
*/
|
|
941
|
+
async addToCart(card, btn, variantId) {
|
|
942
|
+
this.beacon(card.botRef);
|
|
943
|
+
btn.dataset.state = "busy";
|
|
944
|
+
let handled = false;
|
|
945
|
+
try {
|
|
946
|
+
if (this.onAddToCart) {
|
|
947
|
+
handled = await this.onAddToCart({
|
|
948
|
+
productId: card.productId,
|
|
949
|
+
variantId: variantId ?? null,
|
|
950
|
+
quantity: 1
|
|
951
|
+
}) !== false;
|
|
952
|
+
} else {
|
|
953
|
+
const ev = new CustomEvent("brainerce:bot:add-to-cart", {
|
|
954
|
+
detail: {
|
|
955
|
+
productId: card.productId,
|
|
956
|
+
variantId: variantId ?? null,
|
|
957
|
+
quantity: 1,
|
|
958
|
+
connectionId: this.connectionId
|
|
959
|
+
},
|
|
960
|
+
cancelable: true,
|
|
961
|
+
bubbles: true,
|
|
962
|
+
composed: true
|
|
963
|
+
});
|
|
964
|
+
handled = !window.dispatchEvent(ev);
|
|
965
|
+
}
|
|
966
|
+
} catch {
|
|
967
|
+
handled = false;
|
|
968
|
+
}
|
|
969
|
+
if (handled) {
|
|
970
|
+
btn.dataset.state = "done";
|
|
971
|
+
btn.replaceChildren(icon("check"), document.createTextNode(this.t("added")));
|
|
972
|
+
setTimeout(() => {
|
|
973
|
+
if (!this.destroyed && btn.isConnected) {
|
|
974
|
+
delete btn.dataset.state;
|
|
975
|
+
btn.replaceChildren(icon("cart"), document.createTextNode(this.t("addToCart")));
|
|
976
|
+
}
|
|
977
|
+
}, 2200);
|
|
978
|
+
} else {
|
|
979
|
+
delete btn.dataset.state;
|
|
980
|
+
if (isSafeUrl(card.url)) window.location.href = card.url;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
/** The durable conversion signal — fire-and-forget, never blocks. */
|
|
984
|
+
beacon(botRef) {
|
|
985
|
+
try {
|
|
986
|
+
navigator.sendBeacon?.(
|
|
987
|
+
`${this.baseUrl}/api/storefront-bot/attribution/click`,
|
|
988
|
+
new Blob([JSON.stringify({ botRef })], { type: "application/json" })
|
|
989
|
+
);
|
|
990
|
+
} catch {
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
// --------------------------------------------------------------------------
|
|
994
|
+
// Escalation
|
|
413
995
|
// --------------------------------------------------------------------------
|
|
414
996
|
toggleEscalation() {
|
|
415
997
|
this.root?.querySelector(".bb-esc")?.classList.toggle("open");
|
|
@@ -436,50 +1018,18 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
436
1018
|
if (res.ok) {
|
|
437
1019
|
const note = document.createElement("span");
|
|
438
1020
|
note.className = "bb-esc-note";
|
|
439
|
-
note.
|
|
1021
|
+
note.appendChild(icon("check"));
|
|
1022
|
+
note.appendChild(document.createTextNode(this.t("sent")));
|
|
440
1023
|
form.replaceChildren(note);
|
|
441
1024
|
}
|
|
442
1025
|
} catch {
|
|
443
1026
|
}
|
|
444
1027
|
}
|
|
445
|
-
appendCard(card) {
|
|
446
|
-
if (!this.messagesEl) return;
|
|
447
|
-
const a = document.createElement("a");
|
|
448
|
-
a.className = "bb-card";
|
|
449
|
-
a.href = isSafeUrl(card.url) ? card.url : "#";
|
|
450
|
-
if (card.imageUrl && isSafeUrl(card.imageUrl)) {
|
|
451
|
-
const img = document.createElement("img");
|
|
452
|
-
img.src = card.imageUrl;
|
|
453
|
-
img.alt = "";
|
|
454
|
-
a.appendChild(img);
|
|
455
|
-
}
|
|
456
|
-
const body = document.createElement("span");
|
|
457
|
-
body.className = "bb-card-body";
|
|
458
|
-
const title = document.createElement("p");
|
|
459
|
-
title.className = "bb-card-title";
|
|
460
|
-
title.textContent = card.title;
|
|
461
|
-
const priceEl = document.createElement("span");
|
|
462
|
-
priceEl.className = "bb-card-price";
|
|
463
|
-
priceEl.textContent = card.price.formatted;
|
|
464
|
-
body.appendChild(title);
|
|
465
|
-
body.appendChild(priceEl);
|
|
466
|
-
a.appendChild(body);
|
|
467
|
-
a.addEventListener("click", () => {
|
|
468
|
-
try {
|
|
469
|
-
navigator.sendBeacon?.(
|
|
470
|
-
`${this.baseUrl}/api/storefront-bot/attribution/click`,
|
|
471
|
-
new Blob([JSON.stringify({ botRef: card.botRef })], { type: "application/json" })
|
|
472
|
-
);
|
|
473
|
-
} catch {
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
this.messagesEl.appendChild(a);
|
|
477
|
-
this.scrollDown();
|
|
478
|
-
}
|
|
479
1028
|
// --------------------------------------------------------------------------
|
|
480
1029
|
appendMessage(kind, text) {
|
|
481
1030
|
const el = document.createElement("div");
|
|
482
1031
|
el.className = `bb-msg ${kind}`;
|
|
1032
|
+
el.setAttribute("dir", "auto");
|
|
483
1033
|
el.textContent = text;
|
|
484
1034
|
this.messagesEl?.appendChild(el);
|
|
485
1035
|
this.scrollDown();
|
|
@@ -488,7 +1038,14 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
488
1038
|
appendTyping() {
|
|
489
1039
|
const el = document.createElement("div");
|
|
490
1040
|
el.className = "bb-typing";
|
|
491
|
-
|
|
1041
|
+
const dots = document.createElement("span");
|
|
1042
|
+
dots.className = "bb-dots";
|
|
1043
|
+
for (let i = 0; i < 3; i++) dots.appendChild(document.createElement("i"));
|
|
1044
|
+
const tool = document.createElement("span");
|
|
1045
|
+
tool.className = "bb-tool";
|
|
1046
|
+
tool.textContent = this.t("searching");
|
|
1047
|
+
el.appendChild(dots);
|
|
1048
|
+
el.appendChild(tool);
|
|
492
1049
|
this.messagesEl?.appendChild(el);
|
|
493
1050
|
this.scrollDown();
|
|
494
1051
|
return el;
|
|
@@ -497,9 +1054,6 @@ var BrainerceBot = class _BrainerceBot {
|
|
|
497
1054
|
if (this.messagesEl) this.messagesEl.scrollTop = this.messagesEl.scrollHeight;
|
|
498
1055
|
}
|
|
499
1056
|
};
|
|
500
|
-
function isSafeUrl(url) {
|
|
501
|
-
return /^\/(?!\/)/.test(url) || /^https?:\/\//i.test(url);
|
|
502
|
-
}
|
|
503
1057
|
export {
|
|
504
1058
|
BrainerceBot
|
|
505
1059
|
};
|