@zabbu/chat-widget 0.9.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zabbu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,478 @@
1
+ :root {
2
+ --chat-primary-color: #2563eb;
3
+ --chat-bg: #ffffff;
4
+ --chat-text: #111827;
5
+ --chat-bubble-bot-bg: #f3f4f6;
6
+ --chat-bubble-admin-bg: #e0e7ff;
7
+ --chat-border: #e5e7eb;
8
+ --chat-input-border: #d1d5db;
9
+ --chat-panel-bg: #ffffff;
10
+ --chat-label-muted: #6b7280;
11
+ --chat-card-bg: rgba(255, 255, 255, 0.9);
12
+ --chat-card-border: rgba(0, 0, 0, 0.06);
13
+ --chat-chip-bg: rgba(255, 255, 255, 0.7);
14
+ }
15
+
16
+ /* Set by main.ts from the site's own colorScheme — a base palette swap, independent of
17
+ backgroundStyle, which still layers its primaryColor-derived gradient on top of
18
+ whichever of these `--chat-bg`/`--chat-panel-bg` ends up as. */
19
+ body[data-theme='dark'] {
20
+ --chat-bg: #17181f;
21
+ --chat-text: #f1eff8;
22
+ --chat-bubble-bot-bg: #26262f;
23
+ --chat-bubble-admin-bg: #2c2740;
24
+ --chat-border: #333340;
25
+ --chat-input-border: #3a3a48;
26
+ --chat-panel-bg: #17181f;
27
+ --chat-label-muted: #9a97ab;
28
+ --chat-card-bg: rgba(38, 38, 47, 0.9);
29
+ --chat-card-border: rgba(255, 255, 255, 0.08);
30
+ --chat-chip-bg: rgba(255, 255, 255, 0.06);
31
+ }
32
+
33
+ * {
34
+ box-sizing: border-box;
35
+ }
36
+
37
+ html,
38
+ body,
39
+ #app {
40
+ height: 100%;
41
+ margin: 0;
42
+ }
43
+
44
+ body {
45
+ font-family:
46
+ system-ui,
47
+ -apple-system,
48
+ 'Segoe UI',
49
+ sans-serif;
50
+ background: var(--chat-bg);
51
+ color: var(--chat-text);
52
+ }
53
+
54
+ .chat-shell {
55
+ display: flex;
56
+ flex-direction: column;
57
+ height: 100%;
58
+ }
59
+
60
+ .chat-header {
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: space-between;
64
+ padding: 12px 16px;
65
+ background: var(--chat-primary-color);
66
+ color: #fff;
67
+ }
68
+
69
+ .chat-header-title {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 8px;
73
+ }
74
+
75
+ .chat-header img {
76
+ height: 24px;
77
+ width: 24px;
78
+ border-radius: 50%;
79
+ object-fit: cover;
80
+ }
81
+
82
+ .chat-header-avatar {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ width: 24px;
87
+ height: 24px;
88
+ border-radius: 50%;
89
+ background: rgba(255, 255, 255, 0.2);
90
+ font-size: 12px;
91
+ font-weight: 600;
92
+ flex-shrink: 0;
93
+ }
94
+
95
+ .chat-header-avatar-wrap {
96
+ position: relative;
97
+ display: inline-flex;
98
+ flex-shrink: 0;
99
+ }
100
+
101
+ .chat-header-presence {
102
+ position: absolute;
103
+ bottom: -1px;
104
+ right: -1px;
105
+ width: 8px;
106
+ height: 8px;
107
+ border-radius: 50%;
108
+ background: #22c55e;
109
+ /* Same color as .chat-header's own background — reads as a cutout ring around the
110
+ dot rather than a border, the same trick Slack/Discord-style presence dots use. */
111
+ border: 2px solid var(--chat-primary-color);
112
+ }
113
+
114
+ .chat-header-text {
115
+ display: flex;
116
+ flex-direction: column;
117
+ min-width: 0;
118
+ line-height: 1.25;
119
+ }
120
+
121
+ .chat-header-name {
122
+ overflow: hidden;
123
+ text-overflow: ellipsis;
124
+ white-space: nowrap;
125
+ }
126
+
127
+ .chat-header-subtitle {
128
+ font-size: 11px;
129
+ opacity: 0.85;
130
+ }
131
+
132
+ .chat-close-button {
133
+ background: transparent;
134
+ border: none;
135
+ color: #fff;
136
+ font-size: 18px;
137
+ cursor: pointer;
138
+ }
139
+
140
+ .chat-messages {
141
+ flex: 1;
142
+ overflow-y: auto;
143
+ padding: 12px;
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 8px;
147
+ }
148
+
149
+ .chat-bubble {
150
+ max-width: 80%;
151
+ padding: 8px 12px;
152
+ border-radius: 12px;
153
+ white-space: pre-wrap;
154
+ word-break: break-word;
155
+ font-size: 14px;
156
+ line-height: 1.4;
157
+ }
158
+
159
+ .chat-bubble--visitor {
160
+ align-self: flex-end;
161
+ background: var(--chat-primary-color);
162
+ color: #fff;
163
+ }
164
+
165
+ .chat-bubble--bot {
166
+ align-self: flex-start;
167
+ background: var(--chat-bubble-bot-bg);
168
+ color: var(--chat-text);
169
+ }
170
+
171
+ .chat-bubble--admin {
172
+ align-self: flex-start;
173
+ background: var(--chat-bubble-admin-bg);
174
+ color: var(--chat-text);
175
+ }
176
+
177
+ .chat-bubble-label {
178
+ font-size: 10px;
179
+ font-weight: 600;
180
+ text-transform: uppercase;
181
+ letter-spacing: 0.03em;
182
+ opacity: 0.6;
183
+ margin-bottom: 2px;
184
+ }
185
+
186
+ .chat-attachment-image-link {
187
+ display: block;
188
+ margin-top: 6px;
189
+ }
190
+
191
+ .chat-attachment-image {
192
+ max-width: 100%;
193
+ max-height: 160px;
194
+ border-radius: 8px;
195
+ display: block;
196
+ }
197
+
198
+ .chat-attachment-file {
199
+ display: inline-block;
200
+ margin-top: 6px;
201
+ color: inherit;
202
+ text-decoration: underline;
203
+ font-size: 13px;
204
+ }
205
+
206
+ .chat-typing-indicator {
207
+ display: flex;
208
+ align-items: center;
209
+ gap: 4px;
210
+ padding: 10px 12px;
211
+ }
212
+
213
+ .chat-typing-dot {
214
+ width: 6px;
215
+ height: 6px;
216
+ border-radius: 50%;
217
+ background: currentColor;
218
+ opacity: 0.5;
219
+ animation: chat-typing-bounce 1.2s infinite ease-in-out;
220
+ }
221
+
222
+ .chat-typing-dot:nth-child(2) {
223
+ animation-delay: 0.15s;
224
+ }
225
+
226
+ .chat-typing-dot:nth-child(3) {
227
+ animation-delay: 0.3s;
228
+ }
229
+
230
+ @keyframes chat-typing-bounce {
231
+ 0%,
232
+ 60%,
233
+ 100% {
234
+ transform: translateY(0);
235
+ opacity: 0.4;
236
+ }
237
+ 30% {
238
+ transform: translateY(-3px);
239
+ opacity: 1;
240
+ }
241
+ }
242
+
243
+ .chat-input-row {
244
+ display: flex;
245
+ align-items: center;
246
+ border-top: 1px solid var(--chat-border);
247
+ padding: 8px;
248
+ gap: 8px;
249
+ position: relative;
250
+ }
251
+
252
+ .chat-autocomplete-panel {
253
+ position: absolute;
254
+ bottom: calc(100% + 8px);
255
+ left: 8px;
256
+ right: 8px;
257
+ max-height: 200px;
258
+ overflow-y: auto;
259
+ /* --chat-panel-bg, not var(--chat-bg) — that token can be a gradient tinted with
260
+ --chat-primary-color (see BACKGROUND_CSS in dom.ts), which would collide with the
261
+ primary-colored trigger text below. --chat-panel-bg is always flat. */
262
+ background: var(--chat-panel-bg);
263
+ border: 1px solid var(--chat-border);
264
+ border-radius: 10px;
265
+ padding: 4px;
266
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
267
+ z-index: 1;
268
+ }
269
+
270
+ .chat-autocomplete-panel[hidden] {
271
+ display: none;
272
+ }
273
+
274
+ /* Descendant-scoped (not just .chat-autocomplete-option) so this beats the higher-
275
+ specificity ".chat-input-row button" rule these buttons would otherwise inherit,
276
+ being nested inside .chat-input-row itself. */
277
+ .chat-autocomplete-panel .chat-autocomplete-option {
278
+ display: flex;
279
+ align-items: baseline;
280
+ gap: 8px;
281
+ width: 100%;
282
+ background: transparent;
283
+ color: inherit;
284
+ border: none;
285
+ text-align: left;
286
+ padding: 6px 8px;
287
+ border-radius: 6px;
288
+ cursor: pointer;
289
+ font-size: 13px;
290
+ }
291
+
292
+ .chat-autocomplete-panel .chat-autocomplete-option:hover,
293
+ .chat-autocomplete-panel .chat-autocomplete-option--active {
294
+ background: var(--chat-bubble-bot-bg);
295
+ }
296
+
297
+ .chat-autocomplete-trigger {
298
+ font-weight: 600;
299
+ color: var(--chat-primary-color);
300
+ white-space: nowrap;
301
+ }
302
+
303
+ .chat-autocomplete-label {
304
+ color: var(--chat-label-muted);
305
+ overflow: hidden;
306
+ text-overflow: ellipsis;
307
+ white-space: nowrap;
308
+ }
309
+
310
+ .chat-emoji-wrapper {
311
+ position: relative;
312
+ }
313
+
314
+ .chat-emoji-toggle {
315
+ background: transparent;
316
+ border: none;
317
+ font-size: 18px;
318
+ line-height: 1;
319
+ cursor: pointer;
320
+ padding: 6px;
321
+ border-radius: 8px;
322
+ }
323
+
324
+ .chat-emoji-toggle:hover {
325
+ background: var(--chat-bubble-bot-bg);
326
+ }
327
+
328
+ .chat-emoji-panel {
329
+ position: absolute;
330
+ bottom: calc(100% + 8px);
331
+ left: 0;
332
+ display: grid;
333
+ grid-template-columns: repeat(6, 1fr);
334
+ gap: 2px;
335
+ background: var(--chat-panel-bg);
336
+ border: 1px solid var(--chat-border);
337
+ border-radius: 10px;
338
+ padding: 8px;
339
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
340
+ z-index: 1;
341
+ }
342
+
343
+ /* The class rule above sets its own `display`, which otherwise beats the UA
344
+ stylesheet's `[hidden] { display: none }` since author styles always win over it —
345
+ without this override toggling the `hidden` attribute would have no visible effect. */
346
+ .chat-emoji-panel[hidden] {
347
+ display: none;
348
+ }
349
+
350
+ .chat-emoji-option {
351
+ background: transparent;
352
+ border: none;
353
+ font-size: 18px;
354
+ line-height: 1.4;
355
+ cursor: pointer;
356
+ padding: 4px;
357
+ border-radius: 6px;
358
+ }
359
+
360
+ .chat-emoji-option:hover {
361
+ background: var(--chat-bubble-bot-bg);
362
+ }
363
+
364
+ .chat-input-row input {
365
+ flex: 1;
366
+ border: 1px solid var(--chat-input-border);
367
+ border-radius: 8px;
368
+ padding: 8px 10px;
369
+ font-size: 14px;
370
+ background: var(--chat-panel-bg);
371
+ color: var(--chat-text);
372
+ }
373
+
374
+ .chat-input-row button {
375
+ background: var(--chat-primary-color);
376
+ color: #fff;
377
+ border: none;
378
+ border-radius: 8px;
379
+ padding: 8px 14px;
380
+ cursor: pointer;
381
+ }
382
+
383
+ .chat-attach-button {
384
+ background: transparent;
385
+ color: inherit;
386
+ border: none;
387
+ font-size: 18px;
388
+ line-height: 1;
389
+ cursor: pointer;
390
+ padding: 6px;
391
+ border-radius: 8px;
392
+ }
393
+
394
+ .chat-attach-button:hover {
395
+ background: var(--chat-bubble-bot-bg);
396
+ }
397
+
398
+ .chat-welcome-card {
399
+ align-self: center;
400
+ max-width: 92%;
401
+ text-align: center;
402
+ background: var(--chat-card-bg);
403
+ border: 1px solid var(--chat-card-border);
404
+ border-radius: 14px;
405
+ padding: 18px 16px;
406
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
407
+ }
408
+
409
+ .chat-welcome-card-icon {
410
+ width: 42px;
411
+ height: 42px;
412
+ border-radius: 50%;
413
+ background: var(--chat-primary-color);
414
+ color: #fff;
415
+ display: flex;
416
+ align-items: center;
417
+ justify-content: center;
418
+ margin: 0 auto 10px;
419
+ font-size: 18px;
420
+ }
421
+
422
+ .chat-welcome-card-heading {
423
+ margin: 0;
424
+ font-size: 15px;
425
+ font-weight: 700;
426
+ color: var(--chat-text);
427
+ }
428
+
429
+ .chat-welcome-card-body {
430
+ margin: 6px 0 0;
431
+ font-size: 13px;
432
+ line-height: 1.5;
433
+ color: var(--chat-text);
434
+ opacity: 0.75;
435
+ }
436
+
437
+ .chat-quick-replies {
438
+ display: flex;
439
+ flex-wrap: wrap;
440
+ gap: 6px;
441
+ margin-top: 2px;
442
+ }
443
+
444
+ .chat-quick-reply-chip {
445
+ display: inline-flex;
446
+ align-items: center;
447
+ gap: 5px;
448
+ padding: 7px 12px;
449
+ border-radius: 999px;
450
+ border: 1px solid var(--chat-primary-color);
451
+ color: var(--chat-primary-color);
452
+ background: var(--chat-chip-bg);
453
+ font-size: 13px;
454
+ font-weight: 500;
455
+ cursor: pointer;
456
+ }
457
+
458
+ .chat-cta-button {
459
+ align-self: stretch;
460
+ display: flex;
461
+ align-items: center;
462
+ justify-content: center;
463
+ gap: 8px;
464
+ margin-top: 4px;
465
+ padding: 12px 16px;
466
+ border: none;
467
+ border-radius: 10px;
468
+ background: var(--chat-primary-color);
469
+ color: #fff;
470
+ font-size: 14px;
471
+ font-weight: 600;
472
+ cursor: pointer;
473
+ }
474
+
475
+ .chat-quick-reply-chip:hover {
476
+ background: var(--chat-primary-color);
477
+ color: #fff;
478
+ }
@@ -0,0 +1 @@
1
+ "use strict";(()=>{var Z=["\u{1F600}","\u{1F602}","\u{1F609}","\u{1F60A}","\u{1F60D}","\u{1F914}","\u{1F605}","\u{1F622}","\u{1F62E}","\u{1F60E}","\u{1F64F}","\u{1F44D}","\u{1F44E}","\u{1F44F}","\u{1F64C}","\u{1F389}","\u2764\uFE0F","\u{1F525}","\u2705","\u274C","\u26A0\uFE0F","\u{1F4A1}","\u{1F4CE}","\u23F3"];function J(n){return!!n&&n.startsWith("image/")}function P(n,o,t){let e=document.createElement("div");if(e.className=`chat-bubble chat-bubble--${o}`,o==="admin"){let a=document.createElement("div");a.className="chat-bubble-label",a.textContent="Agente",e.appendChild(a)}if(n){let a=document.createElement("div");a.textContent=n,e.appendChild(a)}return t?.attachmentUrl&&e.appendChild(X(t)),e}function X(n){let{attachmentUrl:o,attachmentName:t,attachmentMime:e}=n;if(J(e)){let r=document.createElement("a");r.href=o,r.target="_blank",r.rel="noopener noreferrer",r.className="chat-attachment-image-link";let u=document.createElement("img");return u.src=o,u.alt=t??"",u.className="chat-attachment-image",r.appendChild(u),r}let a=document.createElement("a");return a.href=o,a.target="_blank",a.rel="noopener noreferrer",a.className="chat-attachment-file",a.textContent=`\u{1F4CE} ${t??"Archivo adjunto"}`,a}function L(){let n=document.createElement("div");n.className="chat-bubble chat-bubble--admin chat-typing-indicator",n.setAttribute("aria-live","polite"),n.setAttribute("aria-label","El agente est\xE1 escribiendo");for(let o=0;o<3;o++){let t=document.createElement("span");t.className="chat-typing-dot",n.appendChild(t)}return n}function R(n){let o=document.createElement("div");o.className="chat-emoji-wrapper";let t=document.createElement("button");t.type="button",t.className="chat-emoji-toggle",t.setAttribute("aria-label","Insertar emoji"),t.textContent="\u{1F642}";let e=document.createElement("div");return e.className="chat-emoji-panel",e.hidden=!0,Z.forEach(a=>{let r=document.createElement("button");r.type="button",r.className="chat-emoji-option",r.textContent=a,r.addEventListener("click",()=>{n(a),e.hidden=!0}),e.appendChild(r)}),t.addEventListener("click",()=>{e.hidden=!e.hidden}),document.addEventListener("click",a=>{o.contains(a.target)||(e.hidden=!0)}),o.appendChild(t),o.appendChild(e),{wrapper:o,toggleButton:t}}var ee={SOFT_TINT:"linear-gradient(180deg, color-mix(in srgb, var(--chat-primary-color) 8%, white) 0%, white 100%)",DIAGONAL_WASH:"linear-gradient(135deg, color-mix(in srgb, var(--chat-primary-color) 14%, white) 0%, white 55%, color-mix(in srgb, var(--chat-primary-color) 6%, white) 100%)",RADIAL_GLOW:"radial-gradient(120% 60% at 50% 0%, color-mix(in srgb, var(--chat-primary-color) 18%, white) 0%, white 65%)",DUOTONE_SOFT:"linear-gradient(160deg, color-mix(in srgb, var(--chat-primary-color) 20%, white) 0%, color-mix(in srgb, var(--chat-primary-color) 5%, white) 100%)",MESH_SOFT:"radial-gradient(60% 50% at 15% 10%, color-mix(in srgb, var(--chat-primary-color) 20%, white) 0%, transparent 60%), radial-gradient(50% 45% at 90% 20%, color-mix(in srgb, var(--chat-primary-color) 14%, white) 0%, transparent 60%), radial-gradient(70% 60% at 50% 100%, color-mix(in srgb, var(--chat-primary-color) 10%, white) 0%, transparent 60%), white"},te={SOFT_TINT:"linear-gradient(180deg, color-mix(in srgb, var(--chat-primary-color) 14%, #17181f) 0%, #17181f 100%)",DIAGONAL_WASH:"linear-gradient(135deg, color-mix(in srgb, var(--chat-primary-color) 22%, #17181f) 0%, #17181f 55%, color-mix(in srgb, var(--chat-primary-color) 10%, #17181f) 100%)",RADIAL_GLOW:"radial-gradient(120% 60% at 50% 0%, color-mix(in srgb, var(--chat-primary-color) 26%, #17181f) 0%, #17181f 65%)",DUOTONE_SOFT:"linear-gradient(160deg, color-mix(in srgb, var(--chat-primary-color) 28%, #17181f) 0%, color-mix(in srgb, var(--chat-primary-color) 8%, #17181f) 100%)",MESH_SOFT:"radial-gradient(60% 50% at 15% 10%, color-mix(in srgb, var(--chat-primary-color) 28%, #17181f) 0%, transparent 60%), radial-gradient(50% 45% at 90% 20%, color-mix(in srgb, var(--chat-primary-color) 20%, #17181f) 0%, transparent 60%), radial-gradient(70% 60% at 50% 100%, color-mix(in srgb, var(--chat-primary-color) 16%, #17181f) 0%, transparent 60%), #17181f"};function I(n){n.primaryColor&&document.documentElement.style.setProperty("--chat-primary-color",n.primaryColor);let o=n.colorScheme==="DARK";document.body.dataset.theme=o?"dark":"light";let t=o?te:ee,e=n.backgroundStyle&&t[n.backgroundStyle];e&&document.documentElement.style.setProperty("--chat-bg",e)}function M(n,o){let t=document.createElement("div");t.className="chat-welcome-card";let e=document.createElement("div");e.className="chat-welcome-card-icon",e.textContent="\u{1F4AC}",t.appendChild(e);let a=document.createElement("h4");a.className="chat-welcome-card-heading",a.textContent=`\xA1Hola! Bienvenido a ${n}`,t.appendChild(a);let r=document.createElement("p");return r.className="chat-welcome-card-body",r.textContent=o,t.appendChild(r),t}function O(n){return n==="SLASH"?"/":"@"}function w(n,o,t){let e=document.createElement("div");e.className="chat-autocomplete-panel",e.hidden=!0;let a=0,r=[];function u(){e.replaceChildren(),r.forEach((i,p)=>{let d=document.createElement("button");d.type="button",d.className=p===a?"chat-autocomplete-option chat-autocomplete-option--active":"chat-autocomplete-option";let m=document.createElement("span");m.className="chat-autocomplete-trigger",m.textContent=`${O(i.symbol)}${i.trigger}`;let y=document.createElement("span");y.className="chat-autocomplete-label",y.textContent=i.label,d.appendChild(m),d.appendChild(y),d.addEventListener("mousedown",h=>{h.preventDefault(),f(i)}),e.appendChild(d)})}function f(i){t(`${O(i.symbol)}${i.trigger} `),g()}function g(){e.hidden=!0,r=[]}function E(){let i=/^([/@])(\S*)$/.exec(n.value);if(!i){g();return}let[,p,d]=i,m=p==="/"?"SLASH":"AT";if(r=o.filter(y=>y.symbol===m&&y.trigger.toLowerCase().startsWith(d.toLowerCase())),r.length===0){g();return}a=0,e.hidden=!1,u()}return n.addEventListener("input",E),n.addEventListener("keydown",i=>{e.hidden||r.length===0||(i.key==="ArrowDown"?(i.preventDefault(),a=(a+1)%r.length,u()):i.key==="ArrowUp"?(i.preventDefault(),a=(a-1+r.length)%r.length,u()):i.key==="Enter"||i.key==="Tab"?(i.preventDefault(),f(r[a])):i.key==="Escape"&&g())}),document.addEventListener("click",i=>{!e.contains(i.target)&&i.target!==n&&g()}),e}var ne={TICKET_CREATE:"\u{1F3AB}",TICKET_LOOKUP:"\u{1F50D}",SHOW_MENU:"\u{1F4CB}",CANNED_REPLY:"\u{1F4AC}",RUN_WORKFLOW:"\u26A1"};function D(n){return n&&ne[n]||"\u{1F4AC}"}function U(n,o){let t=document.createElement("div");return t.className="chat-quick-replies",n.forEach(e=>{let a=document.createElement("button");a.type="button",a.className="chat-quick-reply-chip";let r=e.symbol==="SLASH"?"/":"@";a.textContent=`${D(e.action)} ${e.label}`,a.addEventListener("click",()=>o(`${r}${e.trigger}`)),t.appendChild(a)}),t}function k(n,o,t,e){let a=document.createElement("button");a.type="button",a.className="chat-cta-button";let r=o?D(o.action):"\u{1F4AC}";return a.textContent=`${r} ${n}`,a.addEventListener("click",()=>{if(o){let u=o.symbol==="SLASH"?"/":"@";t(`${u}${o.trigger}`)}else e()}),a}var B="chat:ready";var G="chat:close",$="chat:toggle";var N="chat:send",Y="chat:bot-reply",j="chat:connect-error",W="chat:history",F="chat:admin-message",K="chat:admin-typing",q="chat:agent-presence",_="chat:typing",V="chat:upload-attachment",Q="chat:attachment-uploaded",z="chat:attachment-upload-error";function ae(){let n=new URLSearchParams(location.hash.replace(/^#/,""));return{prefix:n.get("prefix"),apiBase:n.get("api")}}function b(n){window.parent.postMessage(n,"*")}async function oe(n,o){let t=await fetch(`${n}/public/sites/${o}`);if(!t.ok)throw new Error("site config unavailable");return t.json()}function re(n){let o=document.getElementById("app");for(;o.firstChild;)o.removeChild(o.firstChild);let t=document.createElement("div");t.className="chat-shell";let e=document.createElement("div");e.className="chat-header";let a=document.createElement("div");a.className="chat-header-title";let r=document.createElement("span");if(r.className="chat-header-avatar-wrap",n.logoUrl){let h=document.createElement("img");h.src=n.logoUrl,h.alt="",r.appendChild(h)}else{let h=document.createElement("span");h.className="chat-header-avatar",h.textContent=n.name.slice(0,1).toUpperCase(),r.appendChild(h)}a.appendChild(r);let u=document.createElement("div");u.className="chat-header-text";let f=document.createElement("span");f.className="chat-header-name",f.textContent=n.name,u.appendChild(f),a.appendChild(u),e.appendChild(a);let g=document.createElement("button");g.className="chat-close-button",g.type="button",g.setAttribute("aria-label","Cerrar"),g.textContent="\u2715",g.addEventListener("click",()=>b({type:G})),e.appendChild(g);let E=document.createElement("div");E.className="chat-messages";let i=document.createElement("form");i.className="chat-input-row";let p=document.createElement("input");p.type="text",p.placeholder="Escribe un mensaje o /menu",p.autocomplete="off";let d,m;if(n.attachmentsEnabled){d=document.createElement("button"),d.type="button",d.className="chat-attach-button",d.setAttribute("aria-label","Adjuntar archivo"),d.textContent="\u{1F4CE}",m=document.createElement("input"),m.type="file",m.accept="image/png,image/jpeg,image/gif,image/webp,application/pdf",m.style.display="none";let h=m;d.addEventListener("click",()=>h.click()),i.appendChild(d),i.appendChild(m)}if(n.emojiPickerEnabled!==!1){let{wrapper:h}=R(v=>{let A=p.selectionStart??p.value.length,C=p.selectionEnd??p.value.length;p.value=p.value.slice(0,A)+v+p.value.slice(C),p.focus(),p.selectionStart=p.selectionEnd=A+v.length});i.appendChild(h)}let y=document.createElement("button");if(y.type="submit",y.textContent=n.sendButtonText||"Enviar",n.commands?.length){let h=w(p,n.commands,v=>{p.value=v,p.focus()});i.appendChild(h)}return i.appendChild(p),i.appendChild(y),t.appendChild(e),t.appendChild(E),t.appendChild(i),o.appendChild(t),{messages:E,inputRow:i,input:p,attachButton:d,attachInput:m,avatarWrap:r,textCol:u}}function ie(n){let o=Date.now()-new Date(n).getTime(),t=Math.round(o/6e4);if(t<1)return"justo ahora";if(t<60)return`hace ${t} min`;let e=Math.round(t/60);return e<24?`hace ${e} h`:`hace ${Math.round(e/24)} d`}function le(n,o,t){if(n.querySelector(".chat-header-presence")?.remove(),o.querySelector(".chat-header-subtitle")?.remove(),!!t){if(t.viewing){let e=document.createElement("span");e.className="chat-header-presence",e.setAttribute("aria-hidden","true"),n.appendChild(e);let a=document.createElement("span");a.className="chat-header-subtitle",a.textContent=t.agentName?`${t.agentName} est\xE1 en l\xEDnea`:"En l\xEDnea",o.appendChild(a)}else if(t.lastActiveAt){let e=document.createElement("span");e.className="chat-header-subtitle",e.textContent=`\xDAltima vez activo ${ie(t.lastActiveAt)}`,o.appendChild(e)}}}async function ce(){let{prefix:n,apiBase:o}=ae();if(!n||!o){document.body.textContent="Configuraci\xF3n inv\xE1lida.";return}let t;try{t=await oe(o,n)}catch{document.body.textContent="No se pudo cargar el chat.";return}I(t);let{messages:e,inputRow:a,input:r,attachInput:u,avatarWrap:f,textCol:g}=re(t),E=new Map;function i(c){let s=crypto.randomUUID();return new Promise((l,T)=>{E.set(s,{resolve:l,reject:T}),b({type:V,payload:{requestId:s,file:c}})})}function p(c){return c?.attachmentUrl?.startsWith("/")?{...c,attachmentUrl:`${o}${c.attachmentUrl}`}:c}function d(c,s,l){e.appendChild(P(c,s,p(l))),e.scrollTop=e.scrollHeight}let m=null;function y(c){c&&!m?(m=L(),e.appendChild(m),e.scrollTop=e.scrollHeight):!c&&m&&(m.remove(),m=null)}function h(c){return c==="VISITOR"?"visitor":c==="ADMIN"?"admin":"bot"}function v(){let c=t.openingStyle??"SIMPLE";if(c!=="RULE_DRIVEN"&&t.welcomeText){if(c==="CARD"){e.appendChild(M(t.name,t.welcomeText)),e.scrollTop=e.scrollHeight;return}if(d(t.welcomeText,"bot"),c==="QUICK_REPLIES"&&t.commands?.length&&(e.appendChild(U(t.commands,x)),e.scrollTop=e.scrollHeight),c==="CTA_BUTTON"){let s=t.commands?.[0]??null,l=s?.label||"Iniciar conversaci\xF3n";e.appendChild(k(l,s,x,()=>r.focus())),e.scrollTop=e.scrollHeight}}}window.addEventListener("message",c=>{let s=c.data;if(!(!s||typeof s.type!="string")){if(s.type===Y){let l=s.payload?.text;l&&d(l,"bot")}else if(s.type===F){let l=s.payload;l&&(y(!1),d(l.text,"admin",l))}else if(s.type===K){let l=s.payload;y(!!l?.isTyping)}else if(s.type===Q){let l=s.payload;l&&(E.get(l.requestId)?.resolve(l),E.delete(l.requestId))}else if(s.type===z){let l=s.payload;l&&(E.get(l.requestId)?.reject(new Error(l.message)),E.delete(l.requestId))}else if(s.type===j)d(t.fallbackMessage,"bot");else if(s.type===W){let l=s.payload?.messages??[];l.length===0?v():l.forEach(T=>d(T.text,h(T.sender),T))}else if(s.type===$&&s.payload?.open)r.focus();else if(s.type===q){let l=s.payload?.presence??null;le(f,g,l)}}});let A=!1,C;function S(){C&&(clearTimeout(C),C=void 0),A&&(A=!1,b({type:_,payload:{isTyping:!1}}))}r.addEventListener("input",()=>{A||(A=!0,b({type:_,payload:{isTyping:!0}})),C&&clearTimeout(C),C=setTimeout(S,2e3)});function x(c){c&&(S(),d(c,"visitor"),b({type:N,payload:{text:c}}),r.value="")}a.addEventListener("submit",c=>{c.preventDefault(),x(r.value.trim())}),u?.addEventListener("change",async()=>{let c=u.files?.[0];if(!c)return;u.value="";let s=r.value.trim();try{let{url:l,name:T,mime:H}=await i(c);d(s,"visitor",{attachmentUrl:l,attachmentName:T,attachmentMime:H}),b({type:N,payload:{text:s,attachmentUrl:l,attachmentName:T,attachmentMime:H}}),r.value=""}catch{d("No se pudo subir el archivo. Intenta de nuevo.","bot")}}),b({type:B,payload:{triggerIcon:t.triggerIcon,soundEnabled:t.soundEnabled,soundTriggers:t.soundTriggers,autoOpenAfterSeconds:t.autoOpenAfterSeconds}})}ce();})();
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/chat-ui/dom.ts", "../src/loader/postmessage-contract.ts", "../src/chat-ui/main.ts"],
4
+ "sourcesContent": ["export type BubbleRole = 'visitor' | 'bot' | 'admin';\n\nexport interface BubbleAttachment {\n attachmentUrl?: string | null;\n attachmentName?: string | null;\n attachmentMime?: string | null;\n}\n\n// Curated, fixed set \u2014 no emoji-picker dependency in either this app or the admin portal.\nconst EMOJI_SET = [\n '\uD83D\uDE00', '\uD83D\uDE02', '\uD83D\uDE09', '\uD83D\uDE0A', '\uD83D\uDE0D', '\uD83E\uDD14', '\uD83D\uDE05', '\uD83D\uDE22',\n '\uD83D\uDE2E', '\uD83D\uDE0E', '\uD83D\uDE4F', '\uD83D\uDC4D', '\uD83D\uDC4E', '\uD83D\uDC4F', '\uD83D\uDE4C', '\uD83C\uDF89',\n '\u2764\uFE0F', '\uD83D\uDD25', '\u2705', '\u274C', '\u26A0\uFE0F', '\uD83D\uDCA1', '\uD83D\uDCCE', '\u23F3',\n];\n\nfunction isImageMime(mime: string | null | undefined): boolean {\n return !!mime && mime.startsWith('image/');\n}\n\n// textContent only, always \u2014 never innerHTML. Messages come from visitor input or from\n// admin-configured rule/command responses, neither of which is trusted enough to parse\n// as markup (see specs/chat-widget/PLAN.md req. 8).\nexport function createMessageBubble(\n text: string,\n role: BubbleRole,\n attachment?: BubbleAttachment,\n): HTMLElement {\n const bubble = document.createElement('div');\n bubble.className = `chat-bubble chat-bubble--${role}`;\n\n if (role === 'admin') {\n const label = document.createElement('div');\n label.className = 'chat-bubble-label';\n label.textContent = 'Agente';\n bubble.appendChild(label);\n }\n\n if (text) {\n const textEl = document.createElement('div');\n textEl.textContent = text;\n bubble.appendChild(textEl);\n }\n\n if (attachment?.attachmentUrl) {\n bubble.appendChild(createAttachmentElement(attachment));\n }\n\n return bubble;\n}\n\nfunction createAttachmentElement(attachment: BubbleAttachment): HTMLElement {\n const { attachmentUrl, attachmentName, attachmentMime } = attachment;\n if (isImageMime(attachmentMime)) {\n const link = document.createElement('a');\n link.href = attachmentUrl!;\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n link.className = 'chat-attachment-image-link';\n const img = document.createElement('img');\n img.src = attachmentUrl!;\n img.alt = attachmentName ?? '';\n img.className = 'chat-attachment-image';\n link.appendChild(img);\n return link;\n }\n\n const link = document.createElement('a');\n link.href = attachmentUrl!;\n link.target = '_blank';\n link.rel = 'noopener noreferrer';\n link.className = 'chat-attachment-file';\n link.textContent = `\uD83D\uDCCE ${attachmentName ?? 'Archivo adjunto'}`;\n return link;\n}\n\nexport function createTypingIndicator(): HTMLElement {\n const bubble = document.createElement('div');\n bubble.className = 'chat-bubble chat-bubble--admin chat-typing-indicator';\n bubble.setAttribute('aria-live', 'polite');\n bubble.setAttribute('aria-label', 'El agente est\u00E1 escribiendo');\n for (let i = 0; i < 3; i++) {\n const dot = document.createElement('span');\n dot.className = 'chat-typing-dot';\n bubble.appendChild(dot);\n }\n return bubble;\n}\n\nexport function createEmojiPicker(onSelect: (emoji: string) => void): {\n wrapper: HTMLElement;\n toggleButton: HTMLButtonElement;\n} {\n const wrapper = document.createElement('div');\n wrapper.className = 'chat-emoji-wrapper';\n\n const toggleButton = document.createElement('button');\n toggleButton.type = 'button';\n toggleButton.className = 'chat-emoji-toggle';\n toggleButton.setAttribute('aria-label', 'Insertar emoji');\n toggleButton.textContent = '\uD83D\uDE42';\n\n const panel = document.createElement('div');\n panel.className = 'chat-emoji-panel';\n panel.hidden = true;\n\n EMOJI_SET.forEach((emoji) => {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'chat-emoji-option';\n button.textContent = emoji;\n button.addEventListener('click', () => {\n onSelect(emoji);\n panel.hidden = true;\n });\n panel.appendChild(button);\n });\n\n toggleButton.addEventListener('click', () => {\n panel.hidden = !panel.hidden;\n });\n\n document.addEventListener('click', (event) => {\n if (!wrapper.contains(event.target as Node)) {\n panel.hidden = true;\n }\n });\n\n wrapper.appendChild(toggleButton);\n wrapper.appendChild(panel);\n\n return { wrapper, toggleButton };\n}\n\n// One CSS background value per style, referencing --chat-primary-color via color-mix()\n// so it's always derived from the site's own color \u2014 no second color to configure.\n// SOLID matches the pre-existing flat-white behavior (chat-ui.css's own default). Mixed\n// against `white`, so these are LIGHT-scheme only \u2014 see BACKGROUND_CSS_DARK below.\nconst BACKGROUND_CSS: Record<string, string> = {\n SOFT_TINT:\n 'linear-gradient(180deg, color-mix(in srgb, var(--chat-primary-color) 8%, white) 0%, white 100%)',\n DIAGONAL_WASH:\n 'linear-gradient(135deg, color-mix(in srgb, var(--chat-primary-color) 14%, white) 0%, white 55%, color-mix(in srgb, var(--chat-primary-color) 6%, white) 100%)',\n RADIAL_GLOW:\n 'radial-gradient(120% 60% at 50% 0%, color-mix(in srgb, var(--chat-primary-color) 18%, white) 0%, white 65%)',\n DUOTONE_SOFT:\n 'linear-gradient(160deg, color-mix(in srgb, var(--chat-primary-color) 20%, white) 0%, color-mix(in srgb, var(--chat-primary-color) 5%, white) 100%)',\n MESH_SOFT:\n 'radial-gradient(60% 50% at 15% 10%, color-mix(in srgb, var(--chat-primary-color) 20%, white) 0%, transparent 60%), radial-gradient(50% 45% at 90% 20%, color-mix(in srgb, var(--chat-primary-color) 14%, white) 0%, transparent 60%), radial-gradient(70% 60% at 50% 100%, color-mix(in srgb, var(--chat-primary-color) 10%, white) 0%, transparent 60%), white',\n};\n\n// Same six treatments, mixed against the dark base instead of white \u2014 SOLID again has no\n// entry, it's chat-ui.css's own body[data-theme=\"dark\"] --chat-bg default.\nconst BACKGROUND_CSS_DARK: Record<string, string> = {\n SOFT_TINT:\n 'linear-gradient(180deg, color-mix(in srgb, var(--chat-primary-color) 14%, #17181f) 0%, #17181f 100%)',\n DIAGONAL_WASH:\n 'linear-gradient(135deg, color-mix(in srgb, var(--chat-primary-color) 22%, #17181f) 0%, #17181f 55%, color-mix(in srgb, var(--chat-primary-color) 10%, #17181f) 100%)',\n RADIAL_GLOW:\n 'radial-gradient(120% 60% at 50% 0%, color-mix(in srgb, var(--chat-primary-color) 26%, #17181f) 0%, #17181f 65%)',\n DUOTONE_SOFT:\n 'linear-gradient(160deg, color-mix(in srgb, var(--chat-primary-color) 28%, #17181f) 0%, color-mix(in srgb, var(--chat-primary-color) 8%, #17181f) 100%)',\n MESH_SOFT:\n 'radial-gradient(60% 50% at 15% 10%, color-mix(in srgb, var(--chat-primary-color) 28%, #17181f) 0%, transparent 60%), radial-gradient(50% 45% at 90% 20%, color-mix(in srgb, var(--chat-primary-color) 20%, #17181f) 0%, transparent 60%), radial-gradient(70% 60% at 50% 100%, color-mix(in srgb, var(--chat-primary-color) 16%, #17181f) 0%, transparent 60%), #17181f',\n};\n\nexport function applyTheme(config: {\n primaryColor?: string | null;\n backgroundStyle?: string | null;\n colorScheme?: string | null;\n}): void {\n if (config.primaryColor) {\n document.documentElement.style.setProperty(\n '--chat-primary-color',\n config.primaryColor,\n );\n }\n const isDark = config.colorScheme === 'DARK';\n document.body.dataset.theme = isDark ? 'dark' : 'light';\n const map = isDark ? BACKGROUND_CSS_DARK : BACKGROUND_CSS;\n const bg = config.backgroundStyle && map[config.backgroundStyle];\n if (bg) {\n document.documentElement.style.setProperty('--chat-bg', bg);\n }\n}\n\n// CARD opening style \u2014 richer welcome than a plain bubble, shown once when the chat has\n// no prior history. `welcomeText` still comes from the same site field, just presented\n// with more visual weight.\nexport function createWelcomeCard(siteName: string, welcomeText: string): HTMLElement {\n const card = document.createElement('div');\n card.className = 'chat-welcome-card';\n\n const icon = document.createElement('div');\n icon.className = 'chat-welcome-card-icon';\n icon.textContent = '\uD83D\uDCAC';\n card.appendChild(icon);\n\n const heading = document.createElement('h4');\n heading.className = 'chat-welcome-card-heading';\n heading.textContent = `\u00A1Hola! Bienvenido a ${siteName}`;\n card.appendChild(heading);\n\n const body = document.createElement('p');\n body.className = 'chat-welcome-card-body';\n body.textContent = welcomeText;\n card.appendChild(body);\n\n return card;\n}\n\nexport interface CommandOption {\n symbol: 'SLASH' | 'AT';\n trigger: string;\n label: string;\n}\n\nfunction commandSymbolChar(symbol: CommandOption['symbol']): string {\n return symbol === 'SLASH' ? '/' : '@';\n}\n\n// Live autocomplete for `/` and `@` commands, mirroring command-parser.ts's own matching\n// rule server-side: only when the symbol is the very first character typed, with no space\n// yet (past a space it's args, not a trigger being typed). Attaches directly to `input`'s\n// own events rather than exposing an update method, since keyboard nav (arrows/Enter/Tab)\n// has to intercept the same keydown the send form listens to.\nexport function createCommandAutocomplete(\n input: HTMLInputElement,\n commands: CommandOption[],\n onPick: (text: string) => void,\n): HTMLElement {\n const panel = document.createElement('div');\n panel.className = 'chat-autocomplete-panel';\n panel.hidden = true;\n\n let activeIndex = 0;\n let matches: CommandOption[] = [];\n\n function renderOptions() {\n panel.replaceChildren();\n matches.forEach((command, i) => {\n const option = document.createElement('button');\n option.type = 'button';\n option.className =\n i === activeIndex\n ? 'chat-autocomplete-option chat-autocomplete-option--active'\n : 'chat-autocomplete-option';\n\n const trigger = document.createElement('span');\n trigger.className = 'chat-autocomplete-trigger';\n trigger.textContent = `${commandSymbolChar(command.symbol)}${command.trigger}`;\n\n const label = document.createElement('span');\n label.className = 'chat-autocomplete-label';\n label.textContent = command.label;\n\n option.appendChild(trigger);\n option.appendChild(label);\n // mousedown, not click \u2014 fires before the input blurs, so the selection isn't lost.\n option.addEventListener('mousedown', (event) => {\n event.preventDefault();\n pick(command);\n });\n panel.appendChild(option);\n });\n }\n\n function pick(command: CommandOption) {\n onPick(`${commandSymbolChar(command.symbol)}${command.trigger} `);\n close();\n }\n\n function close() {\n panel.hidden = true;\n matches = [];\n }\n\n function update() {\n const match = /^([/@])(\\S*)$/.exec(input.value);\n if (!match) {\n close();\n return;\n }\n const [, symbolChar, partial] = match;\n const symbol: CommandOption['symbol'] = symbolChar === '/' ? 'SLASH' : 'AT';\n matches = commands.filter(\n (c) => c.symbol === symbol && c.trigger.toLowerCase().startsWith(partial.toLowerCase()),\n );\n if (matches.length === 0) {\n close();\n return;\n }\n activeIndex = 0;\n panel.hidden = false;\n renderOptions();\n }\n\n input.addEventListener('input', update);\n\n input.addEventListener('keydown', (event) => {\n if (panel.hidden || matches.length === 0) {\n return;\n }\n if (event.key === 'ArrowDown') {\n event.preventDefault();\n activeIndex = (activeIndex + 1) % matches.length;\n renderOptions();\n } else if (event.key === 'ArrowUp') {\n event.preventDefault();\n activeIndex = (activeIndex - 1 + matches.length) % matches.length;\n renderOptions();\n } else if (event.key === 'Enter' || event.key === 'Tab') {\n event.preventDefault();\n pick(matches[activeIndex]);\n } else if (event.key === 'Escape') {\n close();\n }\n });\n\n document.addEventListener('click', (event) => {\n if (!panel.contains(event.target as Node) && event.target !== input) {\n close();\n }\n });\n\n return panel;\n}\n\nexport type CommandActionOption =\n | 'TICKET_CREATE'\n | 'TICKET_LOOKUP'\n | 'SHOW_MENU'\n | 'CANNED_REPLY'\n | 'RUN_WORKFLOW';\n\n// Purely decorative \u2014 lets a QUICK_REPLIES chip / the CTA_BUTTON hint at what it does\n// without a per-command icon field to configure. Falls back to a generic bullet.\nconst ACTION_ICON: Record<CommandActionOption, string> = {\n TICKET_CREATE: '\uD83C\uDFAB',\n TICKET_LOOKUP: '\uD83D\uDD0D',\n SHOW_MENU: '\uD83D\uDCCB',\n CANNED_REPLY: '\uD83D\uDCAC',\n RUN_WORKFLOW: '\u26A1',\n};\n\nfunction actionIcon(action?: CommandActionOption | null): string {\n return (action && ACTION_ICON[action]) || '\uD83D\uDCAC';\n}\n\n// QUICK_REPLIES opening style \u2014 the site's active menu commands as tappable chips,\n// alongside the plain welcome bubble. Clicking a chip sends its trigger exactly like the\n// visitor had typed it, so no new server-side handling is needed.\nexport function createQuickReplies(\n commands: {\n symbol: 'SLASH' | 'AT';\n trigger: string;\n label: string;\n action?: CommandActionOption | null;\n }[],\n onPick: (text: string) => void,\n): HTMLElement {\n const row = document.createElement('div');\n row.className = 'chat-quick-replies';\n\n commands.forEach((command) => {\n const chip = document.createElement('button');\n chip.type = 'button';\n chip.className = 'chat-quick-reply-chip';\n const symbolChar = command.symbol === 'SLASH' ? '/' : '@';\n chip.textContent = `${actionIcon(command.action)} ${command.label}`;\n chip.addEventListener('click', () => onPick(`${symbolChar}${command.trigger}`));\n row.appendChild(chip);\n });\n\n return row;\n}\n\n// CTA_BUTTON opening style \u2014 one wide button instead of leaving the input row as the\n// only way in (Zendesk's \"New conversation\" pattern). Fires the site's first active\n// command if it has one, same as tapping the equivalent QUICK_REPLIES chip would;\n// otherwise just focuses the input, since there's nothing more specific to trigger.\nexport function createCtaButton(\n label: string,\n command: { symbol: 'SLASH' | 'AT'; trigger: string; action?: CommandActionOption | null } | null,\n onPick: (text: string) => void,\n onFocusInput: () => void,\n): HTMLElement {\n const button = document.createElement('button');\n button.type = 'button';\n button.className = 'chat-cta-button';\n const icon = command ? actionIcon(command.action) : '\uD83D\uDCAC';\n button.textContent = `${icon} ${label}`;\n button.addEventListener('click', () => {\n if (command) {\n const symbolChar = command.symbol === 'SLASH' ? '/' : '@';\n onPick(`${symbolChar}${command.trigger}`);\n } else {\n onFocusInput();\n }\n });\n return button;\n}\n", "// Shared between loader.ts (host page \u2014 owns the authenticated socket, since only its\n// Origin is real and unspoofable) and chat-ui/main.ts (iframe page \u2014 render-only, relays\n// user input up and server events down). Kept as plain string constants, not\n// classes/instances, so postMessage's structured-clone never has to serialize anything\n// more than JSON-safe data.\nexport const CHAT_READY = \"chat:ready\";\nexport const CHAT_OPEN = \"chat:open\";\nexport const CHAT_CLOSE = \"chat:close\";\nexport const CHAT_TOGGLE = \"chat:toggle\";\nexport const CHAT_RESIZE = \"chat:resize\";\n// iframe -> loader: visitor submitted a message.\nexport const CHAT_SEND = \"chat:send\";\n// loader -> iframe: the socket's 'bot-reply' event, relayed.\nexport const CHAT_BOT_REPLY = \"chat:bot-reply\";\n// loader -> iframe: the socket's 'connect_error' event, relayed.\nexport const CHAT_CONNECT_ERROR = \"chat:connect-error\";\n// loader -> iframe: the visitor's prior thread, relayed from the 'session' event.\nexport const CHAT_HISTORY = \"chat:history\";\n// loader -> iframe: the socket's 'admin-message' event, relayed \u2014 a human reply from the\n// portal, distinct from an automated CHAT_BOT_REPLY.\nexport const CHAT_ADMIN_MESSAGE = \"chat:admin-message\";\n// loader -> iframe: the socket's 'admin-typing' event, relayed.\nexport const CHAT_ADMIN_TYPING = \"chat:admin-typing\";\n// loader -> iframe: the socket's 'agent-presence' event, relayed \u2014 pushed live whenever an\n// admin starts/stops viewing this conversation from the portal. Also carried once inside\n// SessionPayload at connect time, for whatever the state already was.\nexport const CHAT_AGENT_PRESENCE = \"chat:agent-presence\";\n// iframe -> loader: visitor is drafting a reply, relayed to the socket's 'typing' event.\nexport const CHAT_TYPING = \"chat:typing\";\n// iframe -> loader: visitor picked a file to attach \u2014 only the loader (parent page) has\n// the api key and an unspoofable Origin, so it makes the actual upload request.\nexport const CHAT_UPLOAD_ATTACHMENT = \"chat:upload-attachment\";\n// loader -> iframe: upload succeeded/failed, correlated by requestId.\nexport const CHAT_ATTACHMENT_UPLOADED = \"chat:attachment-uploaded\";\nexport const CHAT_ATTACHMENT_UPLOAD_ERROR = \"chat:attachment-upload-error\";\n\nexport interface AttachmentFields {\n attachmentUrl?: string | null;\n attachmentName?: string | null;\n attachmentMime?: string | null;\n}\n\nexport interface HistoryEntry extends AttachmentFields {\n sender: \"VISITOR\" | \"BOT\" | \"ADMIN\";\n text: string;\n}\n\n// null when Site.activeChatEnabled is off \u2014 the widget should render no presence signal\n// at all in that case, not just an \"offline\" one.\nexport interface AgentPresencePayload {\n viewing: boolean;\n agentName: string | null;\n lastActiveAt: string | null;\n}\n\nexport interface SessionPayload {\n visitorId: string;\n welcomeText?: string | null;\n agentPresence?: AgentPresencePayload | null;\n history: HistoryEntry[];\n}\n\n// `event` is only used by the loader's sound engine to pick the right per-event style \u2014\n// the iframe ignores it and just renders `text` as always.\nexport interface BotReplyPayload {\n text: string;\n event?: ChatTriggerEvent;\n}\n\nexport type SoundStyle = \"SOFT\" | \"CLASSIC\" | \"BELL\";\n\n// Mirrors the backend's ChatTriggerEvent enum \u2014 the shared catalog of chat lifecycle\n// points that sound config (and, server-side, event-triggered rules/commands) key off of.\nexport type ChatTriggerEvent =\n | \"CHAT_OPENED\"\n | \"BOT_REPLY\"\n | \"AGENT_MESSAGE\"\n | \"RULE_TRIGGERED\"\n | \"COMMAND_EXECUTED\";\n\nexport type SoundTriggerConfig = Partial<\n Record<ChatTriggerEvent, { enabled: boolean; soundStyle: SoundStyle }>\n>;\n\n// iframe -> loader, carried on CHAT_READY: config the loader itself needs but never\n// fetches on its own \u2014 the iframe is the one that calls the public config endpoint.\nexport interface ReadyPayload {\n triggerIcon?: string | null;\n soundEnabled?: boolean;\n soundTriggers?: {\n event: ChatTriggerEvent;\n enabled: boolean;\n soundStyle: SoundStyle;\n }[];\n autoOpenAfterSeconds?: number | null;\n}\n\nexport interface AdminMessagePayload extends AttachmentFields {\n text: string;\n}\n\nexport interface AdminTypingPayload {\n isTyping: boolean;\n}\n\nexport interface TypingPayload {\n isTyping: boolean;\n}\n\nexport interface SendPayload extends AttachmentFields {\n text: string;\n}\n\nexport interface HistoryPayload {\n messages: HistoryEntry[];\n}\n\nexport interface UploadAttachmentPayload {\n requestId: string;\n file: File;\n}\n\nexport interface AttachmentUploadedPayload {\n requestId: string;\n url: string;\n name: string;\n mime: string;\n}\n\nexport interface AttachmentUploadErrorPayload {\n requestId: string;\n message: string;\n}\n\nexport interface ChatMessageEnvelope {\n type:\n | typeof CHAT_READY\n | typeof CHAT_OPEN\n | typeof CHAT_CLOSE\n | typeof CHAT_TOGGLE\n | typeof CHAT_RESIZE\n | typeof CHAT_SEND\n | typeof CHAT_BOT_REPLY\n | typeof CHAT_CONNECT_ERROR\n | typeof CHAT_HISTORY\n | typeof CHAT_ADMIN_MESSAGE\n | typeof CHAT_ADMIN_TYPING\n | typeof CHAT_AGENT_PRESENCE\n | typeof CHAT_TYPING\n | typeof CHAT_UPLOAD_ATTACHMENT\n | typeof CHAT_ATTACHMENT_UPLOADED\n | typeof CHAT_ATTACHMENT_UPLOAD_ERROR;\n payload?: Record<string, unknown>;\n}\n", "import {\n applyTheme,\n createCommandAutocomplete,\n createCtaButton,\n createEmojiPicker,\n createMessageBubble,\n createQuickReplies,\n createTypingIndicator,\n createWelcomeCard,\n type CommandActionOption,\n} from \"./dom\";\nimport {\n CHAT_ADMIN_MESSAGE,\n CHAT_ADMIN_TYPING,\n CHAT_AGENT_PRESENCE,\n CHAT_ATTACHMENT_UPLOADED,\n CHAT_ATTACHMENT_UPLOAD_ERROR,\n CHAT_BOT_REPLY,\n CHAT_CLOSE,\n CHAT_CONNECT_ERROR,\n CHAT_HISTORY,\n CHAT_READY,\n CHAT_SEND,\n CHAT_TOGGLE,\n CHAT_TYPING,\n CHAT_UPLOAD_ATTACHMENT,\n type AdminMessagePayload,\n type AdminTypingPayload,\n type AgentPresencePayload,\n type AttachmentUploadedPayload,\n type AttachmentUploadErrorPayload,\n type BotReplyPayload,\n type ChatMessageEnvelope,\n type ChatTriggerEvent,\n type HistoryPayload,\n type SoundStyle,\n} from \"../loader/postmessage-contract\";\n\ntype OpeningStyle =\n \"SIMPLE\" | \"CARD\" | \"QUICK_REPLIES\" | \"RULE_DRIVEN\" | \"CTA_BUTTON\";\n\ninterface PublicSiteConfig {\n name: string;\n fallbackMessage: string;\n primaryColor?: string | null;\n logoUrl?: string | null;\n welcomeText?: string | null;\n backgroundStyle?: string | null;\n openingStyle?: OpeningStyle | null;\n colorScheme?: string | null;\n autoOpenAfterSeconds?: number | null;\n triggerIcon?: string | null;\n sendButtonText?: string | null;\n emojiPickerEnabled?: boolean;\n attachmentsEnabled?: boolean;\n soundEnabled?: boolean;\n soundTriggers?: {\n event: ChatTriggerEvent;\n enabled: boolean;\n soundStyle: SoundStyle;\n }[];\n commands?: {\n symbol: \"SLASH\" | \"AT\";\n trigger: string;\n label: string;\n action?: CommandActionOption | null;\n }[];\n}\n\nfunction readParamsFromHash(): {\n prefix: string | null;\n apiBase: string | null;\n} {\n const params = new URLSearchParams(location.hash.replace(/^#/, \"\"));\n return { prefix: params.get(\"prefix\"), apiBase: params.get(\"api\") };\n}\n\nfunction postToParent(message: ChatMessageEnvelope) {\n window.parent.postMessage(message, \"*\");\n}\n\nasync function fetchPublicConfig(\n apiBase: string,\n prefix: string,\n): Promise<PublicSiteConfig> {\n const response = await fetch(`${apiBase}/public/sites/${prefix}`);\n if (!response.ok) {\n throw new Error(\"site config unavailable\");\n }\n return response.json() as Promise<PublicSiteConfig>;\n}\n\nfunction render(config: PublicSiteConfig) {\n const app = document.getElementById(\"app\")!;\n while (app.firstChild) {\n app.removeChild(app.firstChild);\n }\n\n const shell = document.createElement(\"div\");\n shell.className = \"chat-shell\";\n\n const header = document.createElement(\"div\");\n header.className = \"chat-header\";\n\n const title = document.createElement(\"div\");\n title.className = \"chat-header-title\";\n\n const avatarWrap = document.createElement(\"span\");\n avatarWrap.className = \"chat-header-avatar-wrap\";\n if (config.logoUrl) {\n const logo = document.createElement(\"img\");\n logo.src = config.logoUrl;\n logo.alt = \"\";\n avatarWrap.appendChild(logo);\n } else {\n // Same fallback the admin portal's preview always showed \u2014 the widget itself never\n // actually rendered one until now, so a site with no logo looked bare next to it.\n const avatar = document.createElement(\"span\");\n avatar.className = \"chat-header-avatar\";\n avatar.textContent = config.name.slice(0, 1).toUpperCase();\n avatarWrap.appendChild(avatar);\n }\n // No presence dot/subtitle here \u2014 unlike primaryColor/logo, presence isn't known at\n // render() time. It arrives moments later over the socket (see CHAT_AGENT_PRESENCE in\n // main()) and is applied in place by updatePresence, never by re-rendering the header.\n title.appendChild(avatarWrap);\n\n const textCol = document.createElement(\"div\");\n textCol.className = \"chat-header-text\";\n const name = document.createElement(\"span\");\n name.className = \"chat-header-name\";\n name.textContent = config.name;\n textCol.appendChild(name);\n title.appendChild(textCol);\n\n header.appendChild(title);\n\n const closeButton = document.createElement(\"button\");\n closeButton.className = \"chat-close-button\";\n closeButton.type = \"button\";\n closeButton.setAttribute(\"aria-label\", \"Cerrar\");\n closeButton.textContent = \"\u2715\";\n closeButton.addEventListener(\"click\", () =>\n postToParent({ type: CHAT_CLOSE }),\n );\n header.appendChild(closeButton);\n\n const messages = document.createElement(\"div\");\n messages.className = \"chat-messages\";\n\n const inputRow = document.createElement(\"form\");\n inputRow.className = \"chat-input-row\";\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.placeholder = \"Escribe un mensaje o /menu\";\n input.autocomplete = \"off\";\n\n let attachButton: HTMLButtonElement | undefined;\n let attachInput: HTMLInputElement | undefined;\n if (config.attachmentsEnabled) {\n attachButton = document.createElement(\"button\");\n attachButton.type = \"button\";\n attachButton.className = \"chat-attach-button\";\n attachButton.setAttribute(\"aria-label\", \"Adjuntar archivo\");\n attachButton.textContent = \"\uD83D\uDCCE\";\n attachInput = document.createElement(\"input\");\n attachInput.type = \"file\";\n attachInput.accept =\n \"image/png,image/jpeg,image/gif,image/webp,application/pdf\";\n attachInput.style.display = \"none\";\n const capturedAttachInput = attachInput;\n attachButton.addEventListener(\"click\", () => capturedAttachInput.click());\n inputRow.appendChild(attachButton);\n inputRow.appendChild(attachInput);\n }\n\n if (config.emojiPickerEnabled !== false) {\n const { wrapper: emojiWrapper } = createEmojiPicker((emoji) => {\n const start = input.selectionStart ?? input.value.length;\n const end = input.selectionEnd ?? input.value.length;\n input.value =\n input.value.slice(0, start) + emoji + input.value.slice(end);\n input.focus();\n input.selectionStart = input.selectionEnd = start + emoji.length;\n });\n inputRow.appendChild(emojiWrapper);\n }\n\n const sendButton = document.createElement(\"button\");\n sendButton.type = \"submit\";\n sendButton.textContent = config.sendButtonText || \"Enviar\";\n\n if (config.commands?.length) {\n const autocompletePanel = createCommandAutocomplete(\n input,\n config.commands,\n (text) => {\n input.value = text;\n input.focus();\n },\n );\n inputRow.appendChild(autocompletePanel);\n }\n\n inputRow.appendChild(input);\n inputRow.appendChild(sendButton);\n\n shell.appendChild(header);\n shell.appendChild(messages);\n shell.appendChild(inputRow);\n app.appendChild(shell);\n\n return {\n messages,\n inputRow,\n input,\n attachButton,\n attachInput,\n avatarWrap,\n textCol,\n };\n}\n\nfunction formatRelative(iso: string): string {\n const diffMs = Date.now() - new Date(iso).getTime();\n const minutes = Math.round(diffMs / 60_000);\n if (minutes < 1) return \"justo ahora\";\n if (minutes < 60) return `hace ${minutes} min`;\n const hours = Math.round(minutes / 60);\n if (hours < 24) return `hace ${hours} h`;\n const days = Math.round(hours / 24);\n return `hace ${days} d`;\n}\n\n// Applied in place, never by re-rendering the header \u2014 see the comment in render(). A\n// null payload (Site.activeChatEnabled off, or nothing received yet) clears both.\nfunction updatePresence(\n avatarWrap: HTMLElement,\n textCol: HTMLElement,\n payload: AgentPresencePayload | null,\n) {\n avatarWrap.querySelector(\".chat-header-presence\")?.remove();\n textCol.querySelector(\".chat-header-subtitle\")?.remove();\n if (!payload) {\n return;\n }\n\n if (payload.viewing) {\n const dot = document.createElement(\"span\");\n dot.className = \"chat-header-presence\";\n dot.setAttribute(\"aria-hidden\", \"true\");\n avatarWrap.appendChild(dot);\n\n const subtitle = document.createElement(\"span\");\n subtitle.className = \"chat-header-subtitle\";\n subtitle.textContent = payload.agentName\n ? `${payload.agentName} est\u00E1 en l\u00EDnea`\n : \"En l\u00EDnea\";\n textCol.appendChild(subtitle);\n } else if (payload.lastActiveAt) {\n const subtitle = document.createElement(\"span\");\n subtitle.className = \"chat-header-subtitle\";\n subtitle.textContent = `\u00DAltima vez activo ${formatRelative(payload.lastActiveAt)}`;\n textCol.appendChild(subtitle);\n }\n}\n\nasync function main() {\n const { prefix, apiBase } = readParamsFromHash();\n if (!prefix || !apiBase) {\n document.body.textContent = \"Configuraci\u00F3n inv\u00E1lida.\";\n return;\n }\n\n let config: PublicSiteConfig;\n try {\n config = await fetchPublicConfig(apiBase, prefix);\n } catch {\n document.body.textContent = \"No se pudo cargar el chat.\";\n return;\n }\n\n applyTheme(config);\n const { messages, inputRow, input, attachInput, avatarWrap, textCol } =\n render(config);\n\n const pendingUploads = new Map<\n string,\n {\n resolve: (value: AttachmentUploadedPayload) => void;\n reject: (error: Error) => void;\n }\n >();\n function uploadAttachment(file: File): Promise<AttachmentUploadedPayload> {\n const requestId = crypto.randomUUID();\n return new Promise((resolve, reject) => {\n pendingUploads.set(requestId, { resolve, reject });\n postToParent({\n type: CHAT_UPLOAD_ATTACHMENT,\n payload: { requestId, file },\n });\n });\n }\n\n // Attachment URLs come back from the backend relative (`/uploads/...`) \u2014 they resolve\n // fine when the admin portal renders them (same API origin), but this iframe is served\n // from the widget's own static origin, not the API's, so a relative path would 404 there.\n function resolveAttachmentUrl<T extends { attachmentUrl?: string | null }>(\n attachment: T | undefined,\n ): T | undefined {\n if (!attachment?.attachmentUrl?.startsWith(\"/\")) return attachment;\n return {\n ...attachment,\n attachmentUrl: `${apiBase}${attachment.attachmentUrl}`,\n };\n }\n\n function appendBubble(\n text: string,\n role: \"visitor\" | \"bot\" | \"admin\",\n attachment?: {\n attachmentUrl?: string | null;\n attachmentName?: string | null;\n attachmentMime?: string | null;\n },\n ) {\n messages.appendChild(\n createMessageBubble(text, role, resolveAttachmentUrl(attachment)),\n );\n messages.scrollTop = messages.scrollHeight;\n }\n\n let typingIndicator: HTMLElement | null = null;\n function setTypingIndicator(isTyping: boolean) {\n if (isTyping && !typingIndicator) {\n typingIndicator = createTypingIndicator();\n messages.appendChild(typingIndicator);\n messages.scrollTop = messages.scrollHeight;\n } else if (!isTyping && typingIndicator) {\n typingIndicator.remove();\n typingIndicator = null;\n }\n }\n\n function senderToRole(\n sender: \"VISITOR\" | \"BOT\" | \"ADMIN\",\n ): \"visitor\" | \"bot\" | \"admin\" {\n if (sender === \"VISITOR\") return \"visitor\";\n if (sender === \"ADMIN\") return \"admin\";\n return \"bot\";\n }\n\n // Runs once, only when the visitor has no prior history \u2014 mirrors the server-side\n // guard on `chat-opened` (see chat.gateway.ts's handleChatOpened). RULE_DRIVEN renders\n // nothing here: its content arrives later over the socket as a normal CHAT_BOT_REPLY,\n // once the 'chat-opened' event round-trips.\n function renderOpening() {\n const style = config.openingStyle ?? \"SIMPLE\";\n if (style === \"RULE_DRIVEN\") {\n return;\n }\n if (!config.welcomeText) {\n return;\n }\n if (style === \"CARD\") {\n messages.appendChild(createWelcomeCard(config.name, config.welcomeText));\n messages.scrollTop = messages.scrollHeight;\n return;\n }\n appendBubble(config.welcomeText, \"bot\");\n if (style === \"QUICK_REPLIES\" && config.commands?.length) {\n messages.appendChild(createQuickReplies(config.commands, sendMessage));\n messages.scrollTop = messages.scrollHeight;\n }\n if (style === \"CTA_BUTTON\") {\n const first = config.commands?.[0] ?? null;\n const label = first?.label || \"Iniciar conversaci\u00F3n\";\n messages.appendChild(\n createCtaButton(label, first, sendMessage, () => input.focus()),\n );\n messages.scrollTop = messages.scrollHeight;\n }\n }\n\n // The socket itself lives in the parent (loader.ts) \u2014 only its Origin is real and\n // unspoofable (see specs/chat-widget/PLAN.md). This frame only renders and relays.\n window.addEventListener(\n \"message\",\n (event: MessageEvent<ChatMessageEnvelope>) => {\n const message = event.data;\n if (!message || typeof message.type !== \"string\") {\n return;\n }\n if (message.type === CHAT_BOT_REPLY) {\n const text = message.payload?.text as\n BotReplyPayload[\"text\"] | undefined;\n if (text) {\n appendBubble(text, \"bot\");\n }\n } else if (message.type === CHAT_ADMIN_MESSAGE) {\n const payload = message.payload as AdminMessagePayload | undefined;\n if (payload) {\n setTypingIndicator(false);\n appendBubble(payload.text, \"admin\", payload);\n }\n } else if (message.type === CHAT_ADMIN_TYPING) {\n const payload = message.payload as AdminTypingPayload | undefined;\n setTypingIndicator(Boolean(payload?.isTyping));\n } else if (message.type === CHAT_ATTACHMENT_UPLOADED) {\n const payload = message.payload as\n AttachmentUploadedPayload | undefined;\n if (payload) {\n pendingUploads.get(payload.requestId)?.resolve(payload);\n pendingUploads.delete(payload.requestId);\n }\n } else if (message.type === CHAT_ATTACHMENT_UPLOAD_ERROR) {\n const payload = message.payload as\n AttachmentUploadErrorPayload | undefined;\n if (payload) {\n pendingUploads\n .get(payload.requestId)\n ?.reject(new Error(payload.message));\n pendingUploads.delete(payload.requestId);\n }\n } else if (message.type === CHAT_CONNECT_ERROR) {\n appendBubble(config.fallbackMessage, \"bot\");\n } else if (message.type === CHAT_HISTORY) {\n const entries =\n (message.payload as HistoryPayload | undefined)?.messages ?? [];\n if (entries.length === 0) {\n renderOpening();\n } else {\n entries.forEach((entry) =>\n appendBubble(entry.text, senderToRole(entry.sender), entry),\n );\n }\n } else if (message.type === CHAT_TOGGLE && message.payload?.open) {\n input.focus();\n } else if (message.type === CHAT_AGENT_PRESENCE) {\n const presence =\n (\n message.payload as\n { presence?: AgentPresencePayload | null } | undefined\n )?.presence ?? null;\n updatePresence(avatarWrap, textCol, presence);\n }\n },\n );\n\n // Mirrors the admin composer's own debounce (2s of inactivity implies \"stopped typing\").\n let isTypingSignaled = false;\n let typingTimeout: ReturnType<typeof setTimeout> | undefined;\n function stopTypingSignal() {\n if (typingTimeout) {\n clearTimeout(typingTimeout);\n typingTimeout = undefined;\n }\n if (isTypingSignaled) {\n isTypingSignaled = false;\n postToParent({ type: CHAT_TYPING, payload: { isTyping: false } });\n }\n }\n input.addEventListener(\"input\", () => {\n if (!isTypingSignaled) {\n isTypingSignaled = true;\n postToParent({ type: CHAT_TYPING, payload: { isTyping: true } });\n }\n if (typingTimeout) {\n clearTimeout(typingTimeout);\n }\n typingTimeout = setTimeout(stopTypingSignal, 2000);\n });\n\n function sendMessage(text: string) {\n if (!text) {\n return;\n }\n stopTypingSignal();\n appendBubble(text, \"visitor\");\n postToParent({ type: CHAT_SEND, payload: { text } });\n input.value = \"\";\n }\n\n inputRow.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n sendMessage(input.value.trim());\n });\n\n attachInput?.addEventListener(\"change\", async () => {\n const file = attachInput.files?.[0];\n if (!file) {\n return;\n }\n attachInput.value = \"\";\n const text = input.value.trim();\n try {\n const { url, name, mime } = await uploadAttachment(file);\n appendBubble(text, \"visitor\", {\n attachmentUrl: url,\n attachmentName: name,\n attachmentMime: mime,\n });\n postToParent({\n type: CHAT_SEND,\n payload: {\n text,\n attachmentUrl: url,\n attachmentName: name,\n attachmentMime: mime,\n },\n });\n input.value = \"\";\n } catch {\n appendBubble(\"No se pudo subir el archivo. Intenta de nuevo.\", \"bot\");\n }\n });\n\n postToParent({\n type: CHAT_READY,\n payload: {\n triggerIcon: config.triggerIcon,\n soundEnabled: config.soundEnabled,\n soundTriggers: config.soundTriggers,\n autoOpenAfterSeconds: config.autoOpenAfterSeconds,\n },\n });\n}\n\nmain();\n"],
5
+ "mappings": "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",
6
+ "names": ["link"]
7
+ }