@oyehello/chat 0.2.0 → 0.3.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 +16 -2
- package/dist/index.cjs +628 -214
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -15
- package/dist/index.d.ts +30 -15
- package/dist/index.global.js +624 -210
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +625 -211
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -166,7 +166,7 @@ function createChatClient(options) {
|
|
|
166
166
|
seenTurnIds.add(ev.turnId);
|
|
167
167
|
const m = streamingId ? messages.find((x) => x.id === streamingId) : void 0;
|
|
168
168
|
if (m) {
|
|
169
|
-
m.text = ev.text;
|
|
169
|
+
m.text = ev.text || m.text;
|
|
170
170
|
m.streaming = false;
|
|
171
171
|
m.turnId = ev.turnId;
|
|
172
172
|
} else {
|
|
@@ -411,132 +411,369 @@ function renderMarkdown(text) {
|
|
|
411
411
|
// src/styles.ts
|
|
412
412
|
var STYLES = `
|
|
413
413
|
:host {
|
|
414
|
-
--oc-accent
|
|
415
|
-
--oc-
|
|
416
|
-
--oc-
|
|
417
|
-
--oc-
|
|
418
|
-
--oc-
|
|
419
|
-
--oc-
|
|
420
|
-
--oc-
|
|
421
|
-
|
|
422
|
-
|
|
414
|
+
/* accent (consumer-overridable via --oc-accent) + its derivations */
|
|
415
|
+
--oc-ac: var(--oc-accent, #096DD9);
|
|
416
|
+
--oc-btn-rim: color-mix(in oklab, var(--oc-ac) 70%, black);
|
|
417
|
+
--oc-btn-edge: 0 6px 16px -10px color-mix(in oklab, var(--oc-ac) 55%, transparent);
|
|
418
|
+
--oc-danger: #D92D20;
|
|
419
|
+
--oc-ease: cubic-bezier(0.16,1,0.3,1);
|
|
420
|
+
--oc-bounce: cubic-bezier(0.18,0.89,0.32,1.13);
|
|
421
|
+
--oc-mono: 'Geist Mono', ui-monospace, monospace;
|
|
422
|
+
--oc-r-full: 999px;
|
|
423
|
+
|
|
424
|
+
/* light palette (base) */
|
|
425
|
+
--oc-bg: #FFFFFF; --oc-bg-sunk: #F6F8FB;
|
|
426
|
+
--oc-fg: #0A0D12; --oc-fg-muted: rgba(10,13,18,0.66); --oc-fg-dim: rgba(10,13,18,0.44);
|
|
427
|
+
--oc-border: rgba(9,13,20,0.10);
|
|
428
|
+
--oc-accent-text: color-mix(in oklab, var(--oc-ac) 88%, black);
|
|
429
|
+
--oc-tint-1: rgba(9,13,20,0.025); --oc-tint-2: rgba(9,13,20,0.04); --oc-tint-3: rgba(9,13,20,0.055);
|
|
430
|
+
--oc-field-bg: #FFFFFF; --oc-field-border: rgba(9,13,20,0.22);
|
|
431
|
+
--oc-glass-border: rgba(9,13,20,0.12);
|
|
432
|
+
|
|
433
|
+
position: fixed; right: 24px; bottom: 24px; z-index: 2147483000;
|
|
434
|
+
font-family: 'Geist', system-ui, -apple-system, 'Helvetica Neue', sans-serif;
|
|
435
|
+
-webkit-font-smoothing: antialiased;
|
|
436
|
+
}
|
|
437
|
+
@media (prefers-color-scheme: dark) {
|
|
438
|
+
:host(:not([theme="light"])) {
|
|
439
|
+
--oc-bg: #000000; --oc-bg-sunk: #000000;
|
|
440
|
+
--oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
|
|
441
|
+
--oc-border: rgba(255,255,255,0.09);
|
|
442
|
+
--oc-accent-text: color-mix(in oklab, var(--oc-ac) 62%, white);
|
|
443
|
+
--oc-tint-1: rgba(255,255,255,0.03); --oc-tint-2: rgba(255,255,255,0.05); --oc-tint-3: rgba(255,255,255,0.07);
|
|
444
|
+
--oc-field-bg: rgba(255,255,255,0.045); --oc-field-border: rgba(255,255,255,0.20);
|
|
445
|
+
--oc-glass-border: rgba(255,255,255,0.12);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
:host([theme="dark"]) {
|
|
449
|
+
--oc-bg: #000000; --oc-bg-sunk: #000000;
|
|
450
|
+
--oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
|
|
451
|
+
--oc-border: rgba(255,255,255,0.09);
|
|
452
|
+
--oc-accent-text: color-mix(in oklab, var(--oc-ac) 62%, white);
|
|
453
|
+
--oc-tint-1: rgba(255,255,255,0.03); --oc-tint-2: rgba(255,255,255,0.05); --oc-tint-3: rgba(255,255,255,0.07);
|
|
454
|
+
--oc-field-bg: rgba(255,255,255,0.045); --oc-field-border: rgba(255,255,255,0.20);
|
|
455
|
+
--oc-glass-border: rgba(255,255,255,0.12);
|
|
423
456
|
}
|
|
424
457
|
* { box-sizing: border-box; }
|
|
458
|
+
button { font-family: inherit; }
|
|
425
459
|
|
|
426
|
-
|
|
460
|
+
.oc { display: flex; flex-direction: column; align-items: flex-end; gap: 14px; }
|
|
461
|
+
|
|
462
|
+
/* \u2500\u2500 Launcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
427
463
|
.launcher {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
464
|
+
position: relative; flex: none; width: 46px; height: 46px; border-radius: var(--oc-r-full);
|
|
465
|
+
border: 1px solid var(--oc-btn-rim); background: var(--oc-ac);
|
|
466
|
+
box-shadow: 0 8px 22px -8px color-mix(in oklab, var(--oc-ac) 70%, transparent), 0 18px 48px -18px rgba(0,0,0,0.6);
|
|
467
|
+
color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer;
|
|
468
|
+
transition: transform .26s var(--oc-bounce), filter .2s var(--oc-ease); align-self: flex-end;
|
|
431
469
|
}
|
|
432
470
|
.launcher:hover { transform: scale(1.06); }
|
|
433
|
-
.launcher
|
|
434
|
-
.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
471
|
+
.launcher:active { transform: scale(0.93); }
|
|
472
|
+
.ring { pointer-events: none; position: absolute; inset: -1px; border-radius: var(--oc-r-full); border: 2px solid var(--oc-ac); opacity: 0; }
|
|
473
|
+
.oc.ring:not(.open) .ring { animation: w-ring 2.4s ease-out 1.1s 3; }
|
|
474
|
+
.ic-chat, .ic-close { position: absolute; display: flex; align-items: center; justify-content: center; transition: all .3s var(--oc-bounce); }
|
|
475
|
+
.ic-chat { width: 28px; height: 28px; opacity: 1; transform: rotate(0) scale(1); }
|
|
476
|
+
.ic-close { opacity: 0; transform: rotate(90deg) scale(0.6); }
|
|
477
|
+
.oc.open .ic-chat { opacity: 0; transform: rotate(-90deg) scale(0.6); }
|
|
478
|
+
.oc.open .ic-close { opacity: 1; transform: rotate(0) scale(1); }
|
|
479
|
+
.badge {
|
|
480
|
+
position: absolute; top: -2px; right: -2px; min-width: 17px; height: 17px; padding: 0 5px;
|
|
481
|
+
border-radius: var(--oc-r-full); border: 2px solid var(--oc-bg-sunk); background: var(--oc-danger);
|
|
482
|
+
color: #fff; font-family: var(--oc-mono); font-size: 10px; font-weight: 700;
|
|
483
|
+
display: flex; align-items: center; justify-content: center; opacity: 0; transform: scale(0.4);
|
|
484
|
+
transition: all .26s var(--oc-bounce);
|
|
485
|
+
}
|
|
486
|
+
.oc.unread:not(.open) .badge { opacity: 1; transform: scale(1); }
|
|
487
|
+
|
|
488
|
+
/* \u2500\u2500 Teaser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
489
|
+
.teaser {
|
|
490
|
+
position: absolute; right: 0; bottom: 60px; display: none; align-items: center; gap: 11px;
|
|
491
|
+
opacity: 0; transform: translateY(8px) scale(0.94); pointer-events: none;
|
|
492
|
+
transition: opacity .3s var(--oc-ease), transform .38s var(--oc-bounce);
|
|
493
|
+
}
|
|
494
|
+
.oc.has-teaser .teaser { display: flex; }
|
|
495
|
+
.oc.teaser:not(.open) .teaser { opacity: 1; transform: none; pointer-events: auto; }
|
|
496
|
+
.teaser-btn {
|
|
497
|
+
max-width: 250px; display: flex; align-items: center; gap: 10px; padding: 11px 15px;
|
|
498
|
+
border-radius: 16px 16px 5px 16px; border: 1px solid var(--oc-glass-border); background: var(--oc-bg);
|
|
499
|
+
box-shadow: 0 10px 30px -14px rgba(0,0,0,0.5); color: var(--oc-fg); font-size: 13.5px; line-height: 1.45;
|
|
500
|
+
text-align: left; cursor: pointer; transition: transform .2s var(--oc-ease);
|
|
501
|
+
}
|
|
502
|
+
.teaser-btn:hover { transform: translateY(-1px); }
|
|
503
|
+
.teaser-btn .logo { flex: none; width: 24px; height: 24px; border-radius: 6px; overflow: hidden; color: var(--oc-ac); }
|
|
504
|
+
.teaser-x {
|
|
505
|
+
flex: none; width: 22px; height: 22px; border-radius: var(--oc-r-full); border: 1px solid var(--oc-border);
|
|
506
|
+
background: var(--oc-bg); color: var(--oc-fg-dim); display: flex; align-items: center; justify-content: center; cursor: pointer;
|
|
438
507
|
}
|
|
508
|
+
.teaser-x:hover { color: var(--oc-fg); }
|
|
439
509
|
|
|
440
|
-
/* Panel */
|
|
510
|
+
/* \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
441
511
|
.panel {
|
|
442
|
-
position:
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
.hd
|
|
453
|
-
.hd
|
|
454
|
-
.hd .
|
|
455
|
-
.hd
|
|
456
|
-
|
|
457
|
-
.status {
|
|
458
|
-
.
|
|
459
|
-
.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
.
|
|
463
|
-
.
|
|
464
|
-
.
|
|
465
|
-
|
|
466
|
-
.
|
|
467
|
-
.
|
|
512
|
+
position: relative; width: min(384px, calc(100vw - 40px)); height: min(560px, calc(100vh - 108px));
|
|
513
|
+
display: flex; flex-direction: column; border-radius: 20px; border: 1px solid var(--oc-glass-border);
|
|
514
|
+
background: var(--oc-bg); box-shadow: 0 12px 28px -12px rgba(0,0,0,0.28), 0 40px 90px -30px rgba(0,0,0,0.55);
|
|
515
|
+
overflow: hidden; transform-origin: bottom right;
|
|
516
|
+
opacity: 0; transform: translateY(16px) scale(0.94); pointer-events: none; visibility: hidden;
|
|
517
|
+
transition: opacity .24s var(--oc-ease), transform .34s var(--oc-bounce), visibility .34s, width .3s var(--oc-ease), height .3s var(--oc-ease);
|
|
518
|
+
}
|
|
519
|
+
.oc.open .panel { opacity: 1; transform: none; pointer-events: auto; visibility: visible; }
|
|
520
|
+
.oc.max .panel { width: min(720px, calc(100vw - 40px)); height: calc(100vh - 108px); }
|
|
521
|
+
|
|
522
|
+
.hd { position: relative; flex: none; display: flex; align-items: center; gap: 12px; padding: 13px 14px 12px; background: var(--oc-ac); color: #fff; overflow: hidden; }
|
|
523
|
+
.hd-grid { pointer-events: none; position: absolute; inset: 0; background-image: linear-gradient(90deg, rgba(255,255,255,0.11) 1px, transparent 1px), linear-gradient(180deg, rgba(255,255,255,0.11) 1px, transparent 1px); background-size: 26px 26px; }
|
|
524
|
+
.hd .logo { position: relative; flex: none; width: 32px; height: 32px; border-radius: 7px; overflow: hidden; color: rgba(255,255,255,0.22); }
|
|
525
|
+
.hd-txt { position: relative; flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
|
|
526
|
+
.hd-title { font-size: 14.5px; font-weight: 600; letter-spacing: -0.015em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
527
|
+
.hd-status { display: flex; align-items: center; gap: 6px; font-size: 11.5px; opacity: 0.9; }
|
|
528
|
+
.hd-status .dot { width: 6px; height: 6px; border-radius: var(--oc-r-full); background: #7BE3A6; flex: none; }
|
|
529
|
+
.hd-status .t { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
530
|
+
.hd-btn { position: relative; flex: none; width: 28px; height: 28px; border-radius: 8px; border: none; background: rgba(255,255,255,0.16); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background .18s var(--oc-ease); }
|
|
531
|
+
.hd-btn:hover { background: rgba(255,255,255,0.28); }
|
|
532
|
+
.hd-btn .i { position: absolute; display: flex; transition: opacity .2s var(--oc-ease); }
|
|
533
|
+
.i-expand { opacity: 1; } .i-shrink { opacity: 0; }
|
|
534
|
+
.oc.max .i-expand { opacity: 0; } .oc.max .i-shrink { opacity: 1; }
|
|
535
|
+
|
|
536
|
+
.err { flex: none; display: flex; align-items: center; gap: 8px; padding: 8px 16px; font-size: 12px; color: #fff; background: var(--oc-danger); }
|
|
537
|
+
.err .retry { padding: 3px 10px; font-size: 11px; background: rgba(255,255,255,0.2); color: #fff; border: none; border-radius: 6px; cursor: pointer; }
|
|
538
|
+
|
|
539
|
+
/* \u2500\u2500 Message list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
540
|
+
.body { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; padding: 16px 16px 26px; background: var(--oc-bg); scroll-behavior: smooth; }
|
|
541
|
+
.turn { display: flex; flex-direction: column; gap: 6px; animation: w-in .42s var(--oc-ease) both; }
|
|
542
|
+
.turn.bot { align-items: flex-start; } .turn.user { align-items: flex-end; }
|
|
543
|
+
.b { font-size: 13.5px; line-height: 1.55; text-wrap: pretty; word-break: break-word; }
|
|
544
|
+
.turn.bot .b { max-width: 86%; padding: 13px 15px; border-radius: 20px; background: var(--oc-tint-2); color: var(--oc-fg); }
|
|
545
|
+
.turn.user .b { max-width: 82%; padding: 11px 15px; border-radius: var(--oc-r-full); background: var(--oc-ac); color: #fff; }
|
|
546
|
+
.turn.human .b { max-width: 86%; padding: 13px 15px; border-radius: 20px; background: color-mix(in oklab, var(--oc-ac) 16%, transparent); color: var(--oc-fg); }
|
|
547
|
+
.src { display: inline-flex; align-items: center; gap: 7px; padding: 5px 10px 5px 8px; border-radius: var(--oc-r-full); border: 1px solid var(--oc-border); background: var(--oc-tint-1); max-width: 100%; }
|
|
548
|
+
.src .t { min-width: 0; font-size: 11.5px; color: var(--oc-fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
549
|
+
.byline { display: flex; align-items: center; gap: 6px; padding: 0 4px; font-size: 11.5px; color: var(--oc-fg-dim); }
|
|
468
550
|
.human-label { font-size: 11px; font-weight: 600; opacity: .7; margin-bottom: 2px; }
|
|
469
551
|
|
|
470
|
-
/*
|
|
471
|
-
.md > :first-child { margin-top: 0; }
|
|
472
|
-
.md
|
|
473
|
-
.md
|
|
474
|
-
.md
|
|
475
|
-
.md
|
|
476
|
-
.md
|
|
477
|
-
.
|
|
478
|
-
.md code { font-family: ui-monospace, monospace; font-size: 12.5px; background: rgba(0,0,0,.07); padding: 1px 5px; border-radius: 5px; }
|
|
479
|
-
.md pre { background: #0f172a; color: #e2e8f0; padding: 10px 12px; border-radius: 8px; overflow-x: auto; margin: 0 0 8px; }
|
|
480
|
-
.md pre code { background: none; padding: 0; color: inherit; }
|
|
481
|
-
.md blockquote { margin: 0 0 8px; padding-left: 10px; border-left: 3px solid var(--oc-border); color: var(--oc-muted); }
|
|
552
|
+
/* markdown inside a bot bubble */
|
|
553
|
+
.md > :first-child { margin-top: 0; } .md > :last-child { margin-bottom: 0; }
|
|
554
|
+
.md p { margin: 0 0 8px; } .md ul, .md ol { margin: 0 0 8px; padding-left: 20px; } .md li { margin: 2px 0; }
|
|
555
|
+
.md a { color: var(--oc-accent-text); text-decoration: underline; text-underline-offset: 2px; }
|
|
556
|
+
.md code { font-family: var(--oc-mono); font-size: 12.5px; background: var(--oc-tint-3); padding: 1px 5px; border-radius: 5px; }
|
|
557
|
+
.md pre { background: color-mix(in oklab, var(--oc-fg) 10%, transparent); padding: 10px 12px; border-radius: 8px; overflow-x: auto; margin: 0 0 8px; }
|
|
558
|
+
.md pre code { background: none; padding: 0; }
|
|
559
|
+
.md blockquote { margin: 0 0 8px; padding-left: 10px; border-left: 3px solid var(--oc-border); color: var(--oc-fg-muted); }
|
|
482
560
|
.md h1, .md h2, .md h3, .md h4 { margin: 8px 0 6px; font-size: 15px; }
|
|
483
561
|
.md table { border-collapse: collapse; font-size: 13px; margin: 0 0 8px; display: block; overflow-x: auto; }
|
|
484
562
|
.md th, .md td { border: 1px solid var(--oc-border); padding: 4px 8px; }
|
|
485
|
-
.md hr { border: none; border-top: 1px solid var(--oc-border); margin: 8px 0; }
|
|
486
|
-
|
|
487
|
-
.src-row { align-self: flex-start; display: flex; flex-wrap: wrap; gap: 6px; max-width: 85%; }
|
|
488
|
-
.src-chip { font-size: 11px; background: rgba(0,0,0,.06); color: #374151; border-radius: 6px; padding: 2px 8px; cursor: default; }
|
|
489
563
|
.att-thumb { display: block; max-width: 180px; max-height: 180px; border-radius: 8px; margin-bottom: 6px; }
|
|
490
|
-
.att-chip { display: inline-block; font-size: 12px; background:
|
|
491
|
-
.
|
|
564
|
+
.att-chip { display: inline-block; font-size: 12px; background: var(--oc-tint-3); border-radius: 6px; padding: 2px 8px; margin: 0 0 6px; }
|
|
565
|
+
.turn.user .att-chip { background: rgba(255,255,255,0.25); }
|
|
492
566
|
|
|
493
|
-
|
|
494
|
-
.
|
|
495
|
-
|
|
496
|
-
background: var(--oc-accent); color: #fff; border: none; cursor: pointer;
|
|
497
|
-
font-size: 12px; font-weight: 600; padding: 6px 12px; border-radius: 999px;
|
|
498
|
-
box-shadow: 0 4px 14px rgba(0,0,0,.25); display: flex; align-items: center; gap: 6px;
|
|
499
|
-
}
|
|
500
|
-
.pill[hidden] { display: none; }
|
|
501
|
-
.pill svg { width: 13px; height: 13px; }
|
|
567
|
+
.typing { align-self: flex-start; display: flex; align-items: center; gap: 5px; padding: 13px 16px; border-radius: 20px; background: var(--oc-tint-2); animation: w-in .3s var(--oc-ease) both; }
|
|
568
|
+
.typing span { width: 6px; height: 6px; border-radius: var(--oc-r-full); background: var(--oc-fg-muted); animation: w-dot 1.2s ease-in-out infinite; }
|
|
569
|
+
.typing span:nth-child(2) { animation-delay: .15s; } .typing span:nth-child(3) { animation-delay: .3s; }
|
|
502
570
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
.
|
|
511
|
-
.
|
|
571
|
+
.chips { display: flex; flex-wrap: wrap; gap: 7px; padding-top: 2px; animation: w-in .42s var(--oc-ease) both; animation-delay: .3s; }
|
|
572
|
+
.chip { padding: 8px 13px; border-radius: var(--oc-r-full); border: 1px solid color-mix(in oklab, var(--oc-ac) 34%, transparent); background: color-mix(in oklab, var(--oc-ac) 10%, transparent); color: var(--oc-accent-text); font-size: 12px; font-weight: 500; cursor: pointer; white-space: nowrap; transition: all .18s var(--oc-ease); }
|
|
573
|
+
.chip:hover { background: color-mix(in oklab, var(--oc-ac) 18%, transparent); }
|
|
574
|
+
|
|
575
|
+
/* \u2500\u2500 Jump to latest \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
576
|
+
.jump-wrap { position: relative; flex: none; height: 0; z-index: 2; display: flex; justify-content: center; }
|
|
577
|
+
.jump { position: absolute; bottom: 8px; width: 34px; height: 34px; border-radius: var(--oc-r-full); border: 1px solid var(--oc-border); background: var(--oc-bg); box-shadow: 0 8px 20px -10px rgba(0,0,0,0.5); color: var(--oc-fg-muted); display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0; transform: translateY(6px) scale(0.8); pointer-events: none; transition: all .22s var(--oc-ease); }
|
|
578
|
+
.oc.away .jump { opacity: 1; transform: none; pointer-events: auto; }
|
|
579
|
+
.jump:hover { color: var(--oc-fg); border-color: var(--oc-fg-dim); }
|
|
580
|
+
|
|
581
|
+
/* \u2500\u2500 Composer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
582
|
+
.composer { flex: none; display: flex; flex-direction: column; gap: 9px; padding: 12px 14px 13px; background: var(--oc-bg); }
|
|
583
|
+
.field { display: flex; flex-direction: column; gap: 11px; padding: 13px 14px 11px; border-radius: 18px; border: 1px solid var(--oc-field-border); background: var(--oc-field-bg); transition: border-color .18s var(--oc-ease); }
|
|
584
|
+
.oc.focus .field { border-color: var(--oc-ac); }
|
|
585
|
+
.input { width: 100%; border: none; background: transparent; color: var(--oc-fg); font-size: 13.5px; line-height: 1.4; outline: none; padding: 0; resize: none; max-height: 108px; font-family: inherit; }
|
|
586
|
+
.input::placeholder { color: var(--oc-fg-dim); }
|
|
587
|
+
.acts { display: flex; align-items: center; gap: 16px; }
|
|
588
|
+
.act { flex: none; display: flex; border: none; background: transparent; padding: 4px; margin: -4px; color: var(--oc-fg-dim); cursor: pointer; transition: color .18s var(--oc-ease); }
|
|
589
|
+
.act:hover { color: var(--oc-fg-muted); }
|
|
590
|
+
.oc.emoji .act.emoji-btn { color: var(--oc-accent-text); }
|
|
591
|
+
.send { flex: none; margin-left: auto; width: 34px; height: 34px; border-radius: var(--oc-r-full); border: 1px solid var(--oc-border); background: var(--oc-tint-3); color: var(--oc-fg-dim); box-shadow: none; display: flex; align-items: center; justify-content: center; cursor: default; transition: all .22s var(--oc-ease); }
|
|
592
|
+
.oc.ready .send { border-color: var(--oc-btn-rim); background: var(--oc-ac); color: #fff; box-shadow: var(--oc-btn-edge); cursor: pointer; }
|
|
593
|
+
.oc.ready .send:hover { filter: brightness(1.1); }
|
|
594
|
+
.send:active { transform: scale(0.92); }
|
|
595
|
+
|
|
596
|
+
.foot { position: relative; display: none; text-align: center; font-size: 11.5px; line-height: 1.5; color: var(--oc-fg-dim); }
|
|
597
|
+
.oc.has-privacy .foot { display: block; }
|
|
598
|
+
.foot .priv { color: var(--oc-fg-muted); text-decoration: underline; text-underline-offset: 2px; cursor: default; }
|
|
599
|
+
.foot .tip { position: absolute; left: 0; right: 0; bottom: 16px; padding-bottom: 8px; z-index: 7; display: flex; justify-content: flex-end; pointer-events: none; opacity: 0; transition: opacity .18s var(--oc-ease); }
|
|
600
|
+
.oc.max .foot .tip { right: 188px; }
|
|
601
|
+
.foot:hover .tip { opacity: 1; }
|
|
602
|
+
.foot .tip-card { max-width: 100%; width: 262px; padding: 13px 15px; border-radius: 14px; border: 1px solid var(--oc-border); background: var(--oc-bg); box-shadow: 0 8px 22px -12px rgba(0,0,0,0.32); text-align: left; font-size: 11.5px; line-height: 1.55; color: var(--oc-fg-muted); text-wrap: pretty; transform: translateY(6px); transition: transform .24s var(--oc-bounce); }
|
|
603
|
+
.foot:hover .tip-card { transform: none; }
|
|
604
|
+
.foot .tip-card a { color: var(--oc-fg); text-decoration: underline; text-underline-offset: 2px; }
|
|
605
|
+
|
|
606
|
+
/* \u2500\u2500 Emoji picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
607
|
+
.emoji-scrim { position: absolute; inset: 0; z-index: 3; display: none; }
|
|
608
|
+
.oc.emoji .emoji-scrim { display: block; }
|
|
609
|
+
.emoji-panel { position: absolute; left: 10px; right: 10px; bottom: 96px; z-index: 4; max-height: min(340px, 58%); display: flex; flex-direction: column; border-radius: 18px; border: 1px solid var(--oc-border); background: var(--oc-bg); box-shadow: 0 18px 46px -18px rgba(0,0,0,0.6); overflow: hidden; opacity: 0; transform: translateY(10px) scale(0.96); transform-origin: bottom center; pointer-events: none; visibility: hidden; transition: opacity .18s var(--oc-ease), transform .26s var(--oc-bounce), visibility .26s; }
|
|
610
|
+
.oc.emoji .emoji-panel { opacity: 1; transform: none; pointer-events: auto; visibility: visible; }
|
|
611
|
+
.emoji-search { flex: none; display: flex; align-items: center; gap: 11px; padding: 13px 16px; border-bottom: 1px solid var(--oc-border); color: var(--oc-fg-dim); }
|
|
612
|
+
.emoji-search input { flex: 1 1 auto; min-width: 0; border: none; background: transparent; color: var(--oc-fg); font-size: 14.5px; outline: none; font-family: inherit; }
|
|
613
|
+
.emoji-search input::placeholder { color: var(--oc-fg-dim); }
|
|
614
|
+
.emoji-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; padding: 12px 14px 16px; }
|
|
615
|
+
.emoji-grp { display: flex; flex-direction: column; gap: 6px; }
|
|
616
|
+
.emoji-grp + .emoji-grp { padding-top: 8px; }
|
|
617
|
+
.emoji-grp-name { font-family: var(--oc-mono); font-size: 9.5px; font-weight: 600; letter-spacing: 0.15em; text-transform: uppercase; color: var(--oc-fg-dim); }
|
|
618
|
+
.emoji-grid { display: grid; grid-template-columns: repeat(7, minmax(0,1fr)); gap: 2px; }
|
|
619
|
+
.emoji-grid button { display: flex; align-items: center; justify-content: center; height: 34px; border-radius: 8px; border: none; background: transparent; font-size: 20px; line-height: 1; cursor: pointer; transition: background .14s var(--oc-ease); }
|
|
620
|
+
.emoji-grid button:hover { background: var(--oc-tint-3); }
|
|
621
|
+
.emoji-empty { display: none; flex-direction: column; align-items: center; gap: 5px; padding: 26px 10px; text-align: center; }
|
|
622
|
+
.emoji-empty .h { font-size: 14px; color: var(--oc-fg); } .emoji-empty .s { font-size: 12.5px; color: var(--oc-fg-dim); }
|
|
512
623
|
|
|
513
624
|
@media (max-width: 480px) {
|
|
514
625
|
:host { right: 0; bottom: 0; }
|
|
515
|
-
.
|
|
516
|
-
.
|
|
517
|
-
.
|
|
626
|
+
.oc { padding: 0 16px 16px; }
|
|
627
|
+
.panel { position: fixed; inset: 0; width: 100vw; height: 100dvh; border-radius: 0; }
|
|
628
|
+
.oc.max .panel { width: 100vw; height: 100dvh; }
|
|
518
629
|
}
|
|
519
630
|
@media (prefers-reduced-motion: reduce) {
|
|
520
|
-
.launcher, .panel { transition: none; }
|
|
521
|
-
.body { scroll-behavior: auto; }
|
|
631
|
+
.launcher, .panel, .teaser, .turn, .jump, .emoji-panel { transition: none; animation: none; }
|
|
632
|
+
.ring { animation: none; } .body { scroll-behavior: auto; }
|
|
522
633
|
}
|
|
634
|
+
|
|
635
|
+
@keyframes w-in { from { opacity: 0; transform: translateY(10px) scale(0.97); } to { opacity: 1; transform: none; } }
|
|
636
|
+
@keyframes w-dot { 0%,60%,100% { transform: translateY(0); opacity: .45; } 30% { transform: translateY(-4px); opacity: 1; } }
|
|
637
|
+
@keyframes w-ring { 0% { transform: scale(1); opacity: .5; } 70%,100% { transform: scale(1.7); opacity: 0; } }
|
|
523
638
|
`;
|
|
524
639
|
|
|
640
|
+
// src/icons.ts
|
|
641
|
+
var logoMark = (rectFill) => `<svg viewBox="0 0 1032 1024" fill="none" style="display:block;width:100%;height:100%"><rect x="4" width="1024" height="1024" rx="150" fill="${rectFill}"></rect><path d="M430.924 831.644C270.203 859.983 138.14 760.606 111.329 608.551C84.5174 456.497 174.627 317.945 335.348 289.605C496.068 261.266 628.131 360.643 654.943 512.698C681.754 664.752 591.644 803.304 430.924 831.644ZM406.613 693.77C488.549 679.323 518.727 615.513 505.252 539.092C491.777 462.671 441.594 413.031 359.658 427.478C277.722 441.926 247.544 505.736 261.02 582.157C274.495 658.578 324.677 708.218 406.613 693.77ZM870.337 558.389L744.281 580.616C727.055 482.923 674.565 369.515 652.06 241.884L841.143 208.543C863.509 335.387 853.111 460.696 870.337 558.389ZM910.583 740.568L768.771 765.573L745.016 630.852L886.828 605.846L910.583 740.568Z" fill="#fff"></path></svg>`;
|
|
642
|
+
var stroke = (w, path, size = 24) => `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${w}" stroke-linecap="round" stroke-linejoin="round" style="display:block;flex:none">${path}</svg>`;
|
|
643
|
+
var ICON = {
|
|
644
|
+
close: (size = 19) => stroke(2.4, '<path d="M18 6 6 18M6 6l12 12"/>', size),
|
|
645
|
+
minus: stroke(2, '<path d="M6 12h12"/>', 15),
|
|
646
|
+
expand: stroke(2, '<path d="M15 4h5v5M20 4l-6.5 6.5M9 20H4v-5M4 20l6.5-6.5"/>', 15),
|
|
647
|
+
shrink: stroke(2, '<path d="M20 9h-5V4M15 9l5.5-5.5M4 15h5v5M9 15l-5.5 5.5"/>', 15),
|
|
648
|
+
chevronDown: stroke(2.1, '<path d="m6 9 6 6 6-6"/>', 16),
|
|
649
|
+
send: stroke(2.2, '<path d="M12 19V5M6 11l6-6 6 6"/>', 16),
|
|
650
|
+
paperclip: stroke(1.7, '<path d="M21 11.5 12.5 20a5.5 5.5 0 0 1-7.8-7.8l8.5-8.5a3.7 3.7 0 0 1 5.2 5.2l-8.5 8.5a1.8 1.8 0 0 1-2.6-2.6l7.9-7.8"/>', 17),
|
|
651
|
+
emoji: stroke(1.7, '<circle cx="12" cy="12" r="9"/><path d="M8.5 14.5a4.5 4.5 0 0 0 7 0M9 9.5h.01M15 9.5h.01"/>', 17),
|
|
652
|
+
mic: stroke(1.7, '<rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v3"/>', 17),
|
|
653
|
+
search: stroke(1.9, '<circle cx="11" cy="11" r="7.5"/><path d="m21 21-4.3-4.3"/>', 17),
|
|
654
|
+
clear: stroke(2.2, '<path d="M18 6 6 18M6 6l12 12"/>', 15),
|
|
655
|
+
// Two-page "source" glyph for citation chips.
|
|
656
|
+
book: `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" style="display:block;flex:none;color:var(--oc-accent-text)"><path d="M3 5.5A2.5 2.5 0 0 1 5.5 3H11v18H5.5A2.5 2.5 0 0 1 3 18.5Z"/><path d="M21 5.5A2.5 2.5 0 0 0 18.5 3H13v18h5.5a2.5 2.5 0 0 0 2.5-2.5Z"/></svg>`,
|
|
657
|
+
teaserDismiss: stroke(2.4, '<path d="M18 6 6 18M6 6l12 12"/>', 12)
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// src/emoji.ts
|
|
661
|
+
var CATS = [
|
|
662
|
+
{ name: "Frequently used", items: [
|
|
663
|
+
["\u{1F44D}", "thumbs up"],
|
|
664
|
+
["\u{1F64F}", "thanks"],
|
|
665
|
+
["\u{1F44B}", "wave"],
|
|
666
|
+
["\u2705", "done"],
|
|
667
|
+
["\u{1F4DE}", "call"],
|
|
668
|
+
["\u{1F60A}", "smile"],
|
|
669
|
+
["\u2764\uFE0F", "heart"]
|
|
670
|
+
] },
|
|
671
|
+
{ name: "Smileys and emotion", items: [
|
|
672
|
+
["\u{1F600}", "grinning"],
|
|
673
|
+
["\u{1F603}", "smiley"],
|
|
674
|
+
["\u{1F604}", "grin"],
|
|
675
|
+
["\u{1F601}", "beaming"],
|
|
676
|
+
["\u{1F606}", "squinting"],
|
|
677
|
+
["\u{1F605}", "relief"],
|
|
678
|
+
["\u{1F923}", "rolling"],
|
|
679
|
+
["\u{1F602}", "tears of joy"],
|
|
680
|
+
["\u{1F642}", "slightly smiling"],
|
|
681
|
+
["\u{1F643}", "upside down"],
|
|
682
|
+
["\u{1F609}", "wink"],
|
|
683
|
+
["\u{1F607}", "halo"],
|
|
684
|
+
["\u{1F970}", "smiling hearts"],
|
|
685
|
+
["\u{1F60D}", "heart eyes"],
|
|
686
|
+
["\u{1F618}", "kiss"],
|
|
687
|
+
["\u{1F60B}", "yum"],
|
|
688
|
+
["\u{1F914}", "thinking"],
|
|
689
|
+
["\u{1F928}", "raised brow"],
|
|
690
|
+
["\u{1F610}", "neutral"],
|
|
691
|
+
["\u{1F644}", "eye roll"],
|
|
692
|
+
["\u{1F62C}", "grimace"],
|
|
693
|
+
["\u{1F625}", "sad"],
|
|
694
|
+
["\u{1F622}", "crying"],
|
|
695
|
+
["\u{1F62D}", "sobbing"],
|
|
696
|
+
["\u{1F620}", "angry"],
|
|
697
|
+
["\u{1F633}", "flushed"],
|
|
698
|
+
["\u{1F929}", "star struck"],
|
|
699
|
+
["\u{1F634}", "sleeping"]
|
|
700
|
+
] },
|
|
701
|
+
{ name: "Gestures", items: [
|
|
702
|
+
["\u{1F44E}", "thumbs down"],
|
|
703
|
+
["\u{1F44F}", "clap"],
|
|
704
|
+
["\u{1F64C}", "raised hands"],
|
|
705
|
+
["\u{1F91D}", "handshake"],
|
|
706
|
+
["\u{1F44C}", "ok hand"],
|
|
707
|
+
["\u270C\uFE0F", "victory"],
|
|
708
|
+
["\u{1F4AA}", "strong"],
|
|
709
|
+
["\u{1F440}", "eyes"],
|
|
710
|
+
["\u{1F9E0}", "brain"],
|
|
711
|
+
["\u{1F91F}", "love you"],
|
|
712
|
+
["\u261D\uFE0F", "point up"],
|
|
713
|
+
["\u{1F447}", "point down"],
|
|
714
|
+
["\u{1F91A}", "raised back"],
|
|
715
|
+
["\u{1F91E}", "fingers crossed"]
|
|
716
|
+
] },
|
|
717
|
+
{ name: "Work and travel", items: [
|
|
718
|
+
["\u{1F4C5}", "calendar"],
|
|
719
|
+
["\u{1F4C4}", "document"],
|
|
720
|
+
["\u{1F4B0}", "money"],
|
|
721
|
+
["\u{1F4CA}", "chart"],
|
|
722
|
+
["\u{1F4E6}", "package"],
|
|
723
|
+
["\u{1F3E5}", "hospital"],
|
|
724
|
+
["\u{1F3E6}", "bank"],
|
|
725
|
+
["\u{1F69A}", "truck"],
|
|
726
|
+
["\u2708\uFE0F", "plane"],
|
|
727
|
+
["\u{1F4F1}", "phone"],
|
|
728
|
+
["\u{1F4BB}", "laptop"],
|
|
729
|
+
["\u{1F50D}", "search"],
|
|
730
|
+
["\u{1F4CC}", "pin"],
|
|
731
|
+
["\u{1F514}", "bell"]
|
|
732
|
+
] },
|
|
733
|
+
{ name: "Symbols", items: [
|
|
734
|
+
["\u{1F525}", "fire"],
|
|
735
|
+
["\u2728", "sparkles"],
|
|
736
|
+
["\u{1F389}", "celebrate"],
|
|
737
|
+
["\u{1F4AF}", "hundred"],
|
|
738
|
+
["\u26A0\uFE0F", "warning"],
|
|
739
|
+
["\u274C", "cross"],
|
|
740
|
+
["\u2B50", "star"],
|
|
741
|
+
["\u{1F680}", "launch"],
|
|
742
|
+
["\u{1F49A}", "green heart"],
|
|
743
|
+
["\u{1F499}", "blue heart"],
|
|
744
|
+
["\u{1F552}", "clock"],
|
|
745
|
+
["\u{1F4A1}", "idea"],
|
|
746
|
+
["\u{1F512}", "lock"],
|
|
747
|
+
["\u{1F3AF}", "target"]
|
|
748
|
+
] }
|
|
749
|
+
];
|
|
750
|
+
var toGroup = (name, items) => ({
|
|
751
|
+
name,
|
|
752
|
+
items: items.map(([ch, kw]) => ({ ch, name: kw }))
|
|
753
|
+
});
|
|
754
|
+
function emojiGroups(query) {
|
|
755
|
+
const q = query.trim().toLowerCase();
|
|
756
|
+
if (!q) return CATS.map((c) => toGroup(c.name, c.items));
|
|
757
|
+
const hits = [];
|
|
758
|
+
for (const c of CATS) for (const e of c.items) {
|
|
759
|
+
if (e[1].indexOf(q) >= 0 && !hits.some((h) => h[0] === e[0])) hits.push(e);
|
|
760
|
+
}
|
|
761
|
+
if (!hits.length) return [];
|
|
762
|
+
return [toGroup(`${hits.length} result${hits.length === 1 ? "" : "s"}`, hits)];
|
|
763
|
+
}
|
|
764
|
+
|
|
525
765
|
// src/element.ts
|
|
526
|
-
var
|
|
766
|
+
var DEFAULT_STATUS = "Replies in a few seconds";
|
|
767
|
+
var ATTACHMENT_ACCEPT = "image/*,application/pdf,text/plain,text/csv";
|
|
768
|
+
var AWAY_THRESHOLD = 60;
|
|
769
|
+
var HANDOFF_STATUS = {
|
|
527
770
|
ai: null,
|
|
528
771
|
awaiting_human: "Connecting you to a person\u2026",
|
|
529
772
|
human: "You're chatting with a person"
|
|
530
773
|
};
|
|
531
|
-
var ATTACHMENT_ACCEPT = "image/*,application/pdf,text/plain,text/csv";
|
|
532
|
-
var AGENT_TYPING_TEXT = "The agent is typing\u2026";
|
|
533
|
-
var PIN_THRESHOLD = 48;
|
|
534
|
-
var ICON_CHAT = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>';
|
|
535
|
-
var ICON_CLOSE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
536
|
-
var ICON_DOWN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>';
|
|
537
|
-
var ICON_CLIP = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>';
|
|
538
774
|
var OyehelloChatElement = class extends HTMLElement {
|
|
539
|
-
static observedAttributes = ["api-key", "agent-id", "base-url", "title"];
|
|
775
|
+
static observedAttributes = ["api-key", "agent-id", "base-url", "title", "theme"];
|
|
776
|
+
// ── live client state ──
|
|
540
777
|
client = null;
|
|
541
778
|
off = null;
|
|
542
779
|
messages = [];
|
|
@@ -545,35 +782,36 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
545
782
|
handoffState = "ai";
|
|
546
783
|
agentTyping = false;
|
|
547
784
|
lastError = null;
|
|
548
|
-
// registerTools() may run before connectedCallback creates the client — buffer until it exists.
|
|
549
785
|
pendingTools = {};
|
|
550
786
|
pendingFiles = [];
|
|
551
787
|
imagePreviewCache = /* @__PURE__ */ new Map();
|
|
552
788
|
resolvedUrlCache = /* @__PURE__ */ new Map();
|
|
553
|
-
// Rendered-markdown cache for FINALIZED assistant/human messages (keyed by message id) so the whole
|
|
554
|
-
// history isn't re-parsed on every streamed token — only the live streaming message is re-rendered.
|
|
555
789
|
mdCache = /* @__PURE__ */ new Map();
|
|
556
|
-
|
|
557
|
-
|
|
790
|
+
receivedAt = /* @__PURE__ */ new Map();
|
|
791
|
+
// ── widget UI state ──
|
|
792
|
+
uiOpen = false;
|
|
558
793
|
started = false;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
794
|
+
uiMax = false;
|
|
795
|
+
emojiOpen = false;
|
|
796
|
+
emojiQuery = "";
|
|
797
|
+
away = false;
|
|
798
|
+
// reader scrolled up
|
|
562
799
|
unseen = 0;
|
|
563
|
-
|
|
800
|
+
focused = false;
|
|
801
|
+
teaserActive = false;
|
|
564
802
|
renderedCount = 0;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
803
|
+
renderedBotFinal = 0;
|
|
804
|
+
timers = [];
|
|
805
|
+
// ── shadow refs ──
|
|
806
|
+
oc;
|
|
568
807
|
bodyEl;
|
|
569
808
|
textareaEl;
|
|
570
|
-
sendEl;
|
|
571
809
|
fileEl;
|
|
572
|
-
|
|
573
|
-
badgeEl;
|
|
810
|
+
titleEl;
|
|
574
811
|
statusEl;
|
|
575
812
|
errEl;
|
|
576
|
-
|
|
813
|
+
emojiListEl;
|
|
814
|
+
emojiSearchEl;
|
|
577
815
|
connectedCallback() {
|
|
578
816
|
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
579
817
|
const apiKey = this.getAttribute("api-key");
|
|
@@ -590,18 +828,42 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
590
828
|
}
|
|
591
829
|
this.off = this.client.on((e) => this.onClientEvent(e));
|
|
592
830
|
this.renderShell();
|
|
593
|
-
this.
|
|
831
|
+
this.renderMessages();
|
|
832
|
+
this.updateHeader();
|
|
594
833
|
this.updateStatus();
|
|
834
|
+
this.updateClasses();
|
|
595
835
|
let wasOpen = false;
|
|
596
836
|
try {
|
|
597
|
-
wasOpen = sessionStorage.getItem(this.
|
|
837
|
+
wasOpen = sessionStorage.getItem(this.key("open")) === "1";
|
|
598
838
|
} catch {
|
|
599
839
|
}
|
|
600
|
-
if (wasOpen)
|
|
840
|
+
if (wasOpen) {
|
|
841
|
+
this.openPanel();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
this.updateClasses(true);
|
|
845
|
+
this.timers.push(setTimeout(() => {
|
|
846
|
+
this.oc?.classList.remove("ring");
|
|
847
|
+
}, 6e3));
|
|
848
|
+
const teaserText = this.getAttribute("teaser");
|
|
849
|
+
let dismissed = false;
|
|
850
|
+
try {
|
|
851
|
+
dismissed = sessionStorage.getItem(this.key("teaser")) === "0";
|
|
852
|
+
} catch {
|
|
853
|
+
}
|
|
854
|
+
if (teaserText && !dismissed) {
|
|
855
|
+
this.timers.push(setTimeout(() => {
|
|
856
|
+
if (!this.uiOpen) {
|
|
857
|
+
this.teaserActive = true;
|
|
858
|
+
this.updateClasses();
|
|
859
|
+
}
|
|
860
|
+
}, 1400));
|
|
861
|
+
}
|
|
601
862
|
}
|
|
602
863
|
disconnectedCallback() {
|
|
603
864
|
this.off?.();
|
|
604
865
|
void this.client?.end();
|
|
866
|
+
for (const t of this.timers) clearTimeout(t);
|
|
605
867
|
for (const url of this.imagePreviewCache.values()) {
|
|
606
868
|
if (typeof URL.revokeObjectURL === "function") URL.revokeObjectURL(url);
|
|
607
869
|
}
|
|
@@ -611,61 +873,121 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
611
873
|
if (this.client) this.client.registerTools(handlers);
|
|
612
874
|
else Object.assign(this.pendingTools, handlers);
|
|
613
875
|
}
|
|
614
|
-
|
|
615
|
-
return `oyehello-chat
|
|
876
|
+
key(k) {
|
|
877
|
+
return `oyehello-chat:${k}:${this.getAttribute("agent-id") ?? ""}`;
|
|
878
|
+
}
|
|
879
|
+
get suggestions() {
|
|
880
|
+
return (this.getAttribute("suggestions") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
616
881
|
}
|
|
617
882
|
// ── Shell (built once) ─────────────────────────────────────────────────────────────────────────
|
|
618
883
|
renderShell() {
|
|
619
884
|
const root = this.shadowRoot;
|
|
885
|
+
const hasTeaser = !!this.getAttribute("teaser");
|
|
886
|
+
const hasPrivacy = !!this.getAttribute("privacy-url");
|
|
887
|
+
const teaserText = this.getAttribute("teaser") ?? "";
|
|
620
888
|
root.innerHTML = `
|
|
621
889
|
<style>${STYLES}</style>
|
|
622
|
-
<
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
<
|
|
633
|
-
<
|
|
634
|
-
<
|
|
635
|
-
|
|
636
|
-
|
|
890
|
+
<div class="oc${hasTeaser ? " has-teaser" : ""}${hasPrivacy ? " has-privacy" : ""}">
|
|
891
|
+
<div class="panel" role="dialog" aria-label="Chat">
|
|
892
|
+
<div class="hd">
|
|
893
|
+
<span class="hd-grid"></span>
|
|
894
|
+
<span class="logo">${logoMark("currentColor")}</span>
|
|
895
|
+
<span class="hd-txt"><span class="hd-title">Chat</span><span class="hd-status"><span class="dot"></span><span class="t"></span></span></span>
|
|
896
|
+
<button type="button" class="hd-btn max" aria-label="Expand"><span class="i i-expand">${ICON.expand}</span><span class="i i-shrink">${ICON.shrink}</span></button>
|
|
897
|
+
<button type="button" class="hd-btn min" aria-label="Minimise">${ICON.minus}</button>
|
|
898
|
+
</div>
|
|
899
|
+
<div class="err" hidden></div>
|
|
900
|
+
<div class="body"></div>
|
|
901
|
+
<span class="emoji-scrim"></span>
|
|
902
|
+
<div class="emoji-panel">
|
|
903
|
+
<div class="emoji-search">${ICON.search}<input type="text" placeholder="Search emoji\u2026" aria-label="Search emoji" /></div>
|
|
904
|
+
<div class="emoji-list"></div>
|
|
905
|
+
</div>
|
|
906
|
+
<div class="jump-wrap"><button type="button" class="jump" aria-label="Jump to latest">${ICON.chevronDown}</button></div>
|
|
907
|
+
<div class="composer">
|
|
908
|
+
<form class="field">
|
|
909
|
+
<textarea class="input" rows="1" aria-label="message" placeholder="Ask a question\u2026"></textarea>
|
|
910
|
+
<div class="acts">
|
|
911
|
+
<button type="button" class="act attach" aria-label="Attach a file">${ICON.paperclip}</button>
|
|
912
|
+
<input type="file" accept="${ATTACHMENT_ACCEPT}" multiple hidden />
|
|
913
|
+
<button type="button" class="act emoji-btn" aria-label="Insert emoji">${ICON.emoji}</button>
|
|
914
|
+
<button type="button" class="act mic" aria-label="Send a voice note">${ICON.mic}</button>
|
|
915
|
+
<button type="submit" class="send" aria-label="Send">${ICON.send}</button>
|
|
916
|
+
</div>
|
|
917
|
+
</form>
|
|
918
|
+
<div class="foot">
|
|
919
|
+
<span>By chatting with us, you agree to our </span><span class="priv">Privacy Policy</span>
|
|
920
|
+
<span class="tip"><span class="tip-card">By chatting with us, you agree to the monitoring and recording of this chat to deliver our services and processing of your personal data in accordance with our Privacy Policy. <a href="${this.escapeAttr(this.getAttribute("privacy-url") ?? "#")}" target="_blank" rel="noopener noreferrer">See our Privacy Policy</a>.</span></span>
|
|
921
|
+
</div>
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
924
|
+
|
|
925
|
+
<div class="teaser">
|
|
926
|
+
<button type="button" class="teaser-btn"><span class="logo">${logoMark("currentColor")}</span><span>${escapeHtml(teaserText)}</span></button>
|
|
927
|
+
<button type="button" class="teaser-x" aria-label="Dismiss">${ICON.teaserDismiss}</button>
|
|
928
|
+
</div>
|
|
929
|
+
|
|
930
|
+
<button type="button" class="launcher" aria-label="Open chat">
|
|
931
|
+
<span class="ring"></span>
|
|
932
|
+
<span class="ic-chat">${logoMark("var(--oc-ac)")}</span>
|
|
933
|
+
<span class="ic-close">${ICON.close(19)}</span>
|
|
934
|
+
<span class="badge"></span>
|
|
935
|
+
</button>
|
|
637
936
|
</div>`;
|
|
638
|
-
this.
|
|
937
|
+
this.oc = root.querySelector(".oc");
|
|
639
938
|
this.bodyEl = root.querySelector(".body");
|
|
640
|
-
this.textareaEl = root.querySelector("textarea");
|
|
641
|
-
this.sendEl = root.querySelector(".send");
|
|
939
|
+
this.textareaEl = root.querySelector("textarea.input");
|
|
642
940
|
this.fileEl = root.querySelector('input[type="file"]');
|
|
643
|
-
this.
|
|
644
|
-
this.
|
|
645
|
-
this.statusEl = root.querySelector(".status");
|
|
941
|
+
this.titleEl = root.querySelector(".hd-title");
|
|
942
|
+
this.statusEl = root.querySelector(".hd-status .t");
|
|
646
943
|
this.errEl = root.querySelector(".err");
|
|
647
|
-
this.
|
|
648
|
-
root.querySelector(".
|
|
649
|
-
root.querySelector(".
|
|
944
|
+
this.emojiListEl = root.querySelector(".emoji-list");
|
|
945
|
+
this.emojiSearchEl = root.querySelector(".emoji-search input");
|
|
946
|
+
root.querySelector(".launcher").addEventListener("click", () => this.toggle());
|
|
947
|
+
root.querySelector(".hd-btn.min").addEventListener("click", () => this.closePanel());
|
|
948
|
+
root.querySelector(".hd-btn.max").addEventListener("click", () => {
|
|
949
|
+
this.uiMax = !this.uiMax;
|
|
950
|
+
this.updateClasses();
|
|
951
|
+
});
|
|
952
|
+
root.querySelector(".teaser-btn").addEventListener("click", () => this.openPanel());
|
|
953
|
+
root.querySelector(".teaser-x").addEventListener("click", () => this.dismissTeaser());
|
|
954
|
+
root.querySelector(".jump").addEventListener("click", () => this.scrollToBottom());
|
|
650
955
|
root.querySelector(".attach").addEventListener("click", () => this.fileEl.click());
|
|
651
956
|
this.fileEl.addEventListener("change", () => {
|
|
652
957
|
this.pendingFiles = this.fileEl.files ? Array.from(this.fileEl.files) : [];
|
|
653
958
|
});
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
959
|
+
root.querySelector(".emoji-btn").addEventListener("click", () => this.toggleEmoji());
|
|
960
|
+
root.querySelector(".emoji-scrim").addEventListener("click", () => this.closeEmoji());
|
|
961
|
+
this.emojiSearchEl.addEventListener("input", () => {
|
|
962
|
+
this.emojiQuery = this.emojiSearchEl.value;
|
|
963
|
+
this.renderEmoji();
|
|
964
|
+
});
|
|
965
|
+
this.bodyEl.addEventListener("scroll", () => this.onScroll());
|
|
966
|
+
root.querySelector("form.field").addEventListener("submit", (e) => {
|
|
657
967
|
e.preventDefault();
|
|
658
968
|
this.doSend();
|
|
659
969
|
});
|
|
660
|
-
this.textareaEl.addEventListener("input", () =>
|
|
970
|
+
this.textareaEl.addEventListener("input", () => {
|
|
971
|
+
this.autoGrow();
|
|
972
|
+
this.updateClasses();
|
|
973
|
+
});
|
|
974
|
+
this.textareaEl.addEventListener("focus", () => {
|
|
975
|
+
this.focused = true;
|
|
976
|
+
this.updateClasses();
|
|
977
|
+
});
|
|
978
|
+
this.textareaEl.addEventListener("blur", () => {
|
|
979
|
+
this.focused = false;
|
|
980
|
+
this.updateClasses();
|
|
981
|
+
});
|
|
661
982
|
this.textareaEl.addEventListener("keydown", (e) => {
|
|
662
983
|
if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
|
|
663
984
|
e.preventDefault();
|
|
664
985
|
this.doSend();
|
|
665
986
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
987
|
+
if (e.key === "Escape") {
|
|
988
|
+
if (this.emojiOpen) this.closeEmoji();
|
|
989
|
+
else this.closePanel();
|
|
990
|
+
}
|
|
669
991
|
});
|
|
670
992
|
}
|
|
671
993
|
renderError(msg) {
|
|
@@ -673,37 +995,62 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
673
995
|
console.error(msg);
|
|
674
996
|
this.shadowRoot.innerHTML = `<div style="position:fixed;right:16px;bottom:16px;color:#b91c1c;font:12px system-ui;background:#fef2f2;border:1px solid #fecaca;padding:8px 12px;border-radius:8px">${escapeHtml(msg)}</div>`;
|
|
675
997
|
}
|
|
998
|
+
// ── State-class sync ──────────────────────────────────────────────────────────────────────────
|
|
999
|
+
updateClasses(initialRing = false) {
|
|
1000
|
+
if (!this.oc) return;
|
|
1001
|
+
const c = this.oc.classList;
|
|
1002
|
+
c.toggle("open", this.uiOpen);
|
|
1003
|
+
c.toggle("max", this.uiMax);
|
|
1004
|
+
c.toggle("emoji", this.emojiOpen);
|
|
1005
|
+
c.toggle("teaser", this.teaserActive);
|
|
1006
|
+
c.toggle("away", this.away);
|
|
1007
|
+
c.toggle("focus", this.focused);
|
|
1008
|
+
c.toggle("ready", (this.textareaEl?.value.trim().length ?? 0) > 0);
|
|
1009
|
+
c.toggle("unread", this.unseen > 0);
|
|
1010
|
+
if (initialRing) c.add("ring");
|
|
1011
|
+
const badge = this.oc.querySelector(".badge");
|
|
1012
|
+
if (badge) badge.textContent = this.unseen > 9 ? "9+" : String(this.unseen);
|
|
1013
|
+
const launcher = this.oc.querySelector(".launcher");
|
|
1014
|
+
if (launcher) launcher.setAttribute("aria-label", this.uiOpen ? "Close chat" : "Open chat");
|
|
1015
|
+
}
|
|
676
1016
|
// ── Open / close ───────────────────────────────────────────────────────────────────────────────
|
|
677
|
-
|
|
678
|
-
this.
|
|
1017
|
+
toggle() {
|
|
1018
|
+
this.uiOpen ? this.closePanel() : this.openPanel();
|
|
679
1019
|
}
|
|
680
1020
|
openPanel() {
|
|
681
|
-
this.
|
|
1021
|
+
this.uiOpen = true;
|
|
1022
|
+
this.teaserActive = false;
|
|
1023
|
+
this.unseen = 0;
|
|
682
1024
|
try {
|
|
683
|
-
sessionStorage.setItem(this.
|
|
1025
|
+
sessionStorage.setItem(this.key("open"), "1");
|
|
684
1026
|
} catch {
|
|
685
1027
|
}
|
|
686
|
-
this.panelEl.hidden = false;
|
|
687
|
-
this.panelEl.classList.add("enter");
|
|
688
|
-
requestAnimationFrame(() => this.panelEl.classList.remove("enter"));
|
|
689
1028
|
if (!this.started && this.client) {
|
|
690
1029
|
this.started = true;
|
|
691
1030
|
void this.client.start();
|
|
692
1031
|
}
|
|
1032
|
+
this.updateClasses();
|
|
693
1033
|
this.renderMessages();
|
|
694
|
-
this.pinned = true;
|
|
695
|
-
this.unseen = 0;
|
|
696
|
-
this.updateBadge();
|
|
697
1034
|
this.scrollToBottom();
|
|
698
|
-
if (!this.textareaEl.disabled) this.textareaEl.focus();
|
|
1035
|
+
if (!this.textareaEl.disabled) setTimeout(() => this.textareaEl.focus(), 60);
|
|
699
1036
|
}
|
|
700
1037
|
closePanel() {
|
|
701
|
-
this.
|
|
1038
|
+
this.uiOpen = false;
|
|
1039
|
+
this.uiMax = false;
|
|
1040
|
+
this.emojiOpen = false;
|
|
702
1041
|
try {
|
|
703
|
-
sessionStorage.setItem(this.
|
|
1042
|
+
sessionStorage.setItem(this.key("open"), "0");
|
|
704
1043
|
} catch {
|
|
705
1044
|
}
|
|
706
|
-
this.
|
|
1045
|
+
this.updateClasses();
|
|
1046
|
+
}
|
|
1047
|
+
dismissTeaser() {
|
|
1048
|
+
this.teaserActive = false;
|
|
1049
|
+
try {
|
|
1050
|
+
sessionStorage.setItem(this.key("teaser"), "0");
|
|
1051
|
+
} catch {
|
|
1052
|
+
}
|
|
1053
|
+
this.updateClasses();
|
|
707
1054
|
}
|
|
708
1055
|
// ── Client events ─────────────────────────────────────────────────────────────────────────────
|
|
709
1056
|
onClientEvent(e) {
|
|
@@ -713,24 +1060,23 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
713
1060
|
if (e.state === "ready" || e.state === "opening") this.lastError = null;
|
|
714
1061
|
} else if (e.type === "agent") {
|
|
715
1062
|
this.agentName = e.agent.name;
|
|
716
|
-
this.
|
|
1063
|
+
this.updateHeader();
|
|
717
1064
|
} else if (e.type === "handoff") this.handoffState = e.state;
|
|
718
1065
|
else if (e.type === "agentTyping") this.agentTyping = e.active;
|
|
719
1066
|
else if (e.type === "error") this.lastError = e.error.message;
|
|
720
1067
|
this.updateStatus();
|
|
721
1068
|
this.renderMessages();
|
|
722
1069
|
}
|
|
723
|
-
|
|
1070
|
+
updateHeader() {
|
|
724
1071
|
if (this.titleEl) this.titleEl.textContent = this.getAttribute("title") ?? this.agentName ?? "Chat";
|
|
725
1072
|
}
|
|
726
1073
|
updateStatus() {
|
|
727
1074
|
if (!this.textareaEl) return;
|
|
728
1075
|
const disabled = this.state !== "ready";
|
|
729
1076
|
this.textareaEl.disabled = disabled;
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
this.statusEl.
|
|
733
|
-
this.statusEl.textContent = st ?? "";
|
|
1077
|
+
const handoff = HANDOFF_STATUS[this.handoffState];
|
|
1078
|
+
const text = this.state === "opening" ? "Connecting\u2026" : handoff ?? (this.getAttribute("status") ?? DEFAULT_STATUS);
|
|
1079
|
+
if (this.statusEl) this.statusEl.textContent = text;
|
|
734
1080
|
if (this.state === "error" && this.lastError) {
|
|
735
1081
|
this.errEl.hidden = false;
|
|
736
1082
|
this.errEl.innerHTML = `<span>Couldn't start chat: ${escapeHtml(this.lastError)}</span><button class="retry" type="button">Retry</button>`;
|
|
@@ -746,58 +1092,73 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
746
1092
|
renderMessages() {
|
|
747
1093
|
if (!this.bodyEl) return;
|
|
748
1094
|
const body = this.bodyEl;
|
|
749
|
-
const atBottom = this.open && this.pinned;
|
|
750
1095
|
const prevTop = body.scrollTop;
|
|
751
1096
|
const added = this.messages.length - this.renderedCount;
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
1097
|
+
const botFinal = this.messages.filter((m) => m.role === "assistant" && !m.streaming).length;
|
|
1098
|
+
const newBotFinal = botFinal > this.renderedBotFinal;
|
|
1099
|
+
const now = Date.now();
|
|
1100
|
+
for (const m of this.messages) if (!this.receivedAt.has(m.id)) this.receivedAt.set(m.id, now);
|
|
1101
|
+
const lastBot = (() => {
|
|
1102
|
+
for (let i = this.messages.length - 1; i >= 0; i--) if (this.messages[i]?.role === "assistant") return i;
|
|
1103
|
+
return -1;
|
|
1104
|
+
})();
|
|
1105
|
+
let html = this.messages.map((m, i) => this.turnHtml(m, i === lastBot && !this.agentTyping)).join("");
|
|
1106
|
+
if (this.agentTyping) html += '<span class="typing"><span></span><span></span><span></span></span>';
|
|
1107
|
+
const sugg = this.suggestions;
|
|
1108
|
+
if (sugg.length > 0 && !this.agentTyping && this.state === "ready") {
|
|
1109
|
+
html += `<span class="chips">${sugg.map((s, i) => `<button type="button" class="chip" data-chip="${i}">${escapeHtml(s)}</button>`).join("")}</span>`;
|
|
1110
|
+
}
|
|
755
1111
|
body.innerHTML = html;
|
|
756
1112
|
this.hydrateAttachmentImages();
|
|
1113
|
+
this.bindChips();
|
|
757
1114
|
this.renderedCount = this.messages.length;
|
|
758
|
-
|
|
1115
|
+
this.renderedBotFinal = botFinal;
|
|
1116
|
+
if (!this.uiOpen) {
|
|
1117
|
+
if (added > 0) this.unseen += added;
|
|
1118
|
+
this.updateClasses();
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
if (newBotFinal && !this.away) this.parkLastQuestion();
|
|
1122
|
+
else if (!this.away) this.scrollToBottom();
|
|
759
1123
|
else {
|
|
760
|
-
|
|
1124
|
+
body.scrollTop = prevTop;
|
|
761
1125
|
if (added > 0) this.unseen += added;
|
|
762
1126
|
}
|
|
763
|
-
this.
|
|
764
|
-
|
|
1127
|
+
this.updateClasses();
|
|
1128
|
+
}
|
|
1129
|
+
// After a reply lands, float the question that prompted it to the top so the answer reads from its
|
|
1130
|
+
// first line rather than its last (the design's behaviour).
|
|
1131
|
+
parkLastQuestion() {
|
|
1132
|
+
const users = this.bodyEl.querySelectorAll(".turn.user");
|
|
1133
|
+
const last = users[users.length - 1];
|
|
1134
|
+
if (!last) {
|
|
1135
|
+
this.scrollToBottom();
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
const top = Math.max(0, Math.min(last.offsetTop - 22, this.bodyEl.scrollHeight - this.bodyEl.clientHeight));
|
|
1139
|
+
this.bodyEl.scrollTo({ top, behavior: "smooth" });
|
|
765
1140
|
}
|
|
766
|
-
|
|
1141
|
+
onScroll() {
|
|
767
1142
|
const b = this.bodyEl;
|
|
768
|
-
const
|
|
769
|
-
this.
|
|
770
|
-
|
|
771
|
-
this.unseen = 0;
|
|
772
|
-
this.
|
|
1143
|
+
const away = b.scrollHeight - b.scrollTop - b.clientHeight > AWAY_THRESHOLD;
|
|
1144
|
+
if (away !== this.away) {
|
|
1145
|
+
this.away = away;
|
|
1146
|
+
if (!away) this.unseen = 0;
|
|
1147
|
+
this.updateClasses();
|
|
773
1148
|
}
|
|
774
|
-
this.updatePill();
|
|
775
1149
|
}
|
|
776
1150
|
scrollToBottom() {
|
|
777
1151
|
if (!this.bodyEl) return;
|
|
778
1152
|
this.bodyEl.scrollTop = this.bodyEl.scrollHeight;
|
|
779
|
-
this.
|
|
1153
|
+
this.away = false;
|
|
780
1154
|
this.unseen = 0;
|
|
781
|
-
this.
|
|
782
|
-
this.updateBadge();
|
|
783
|
-
}
|
|
784
|
-
updatePill() {
|
|
785
|
-
if (!this.pillEl) return;
|
|
786
|
-
this.pillEl.hidden = !(this.open && !this.pinned && this.unseen > 0);
|
|
787
|
-
const t = this.pillEl.querySelector(".pill-text");
|
|
788
|
-
if (t) t.textContent = this.unseen === 1 ? "1 new message" : `${this.unseen} new messages`;
|
|
789
|
-
}
|
|
790
|
-
updateBadge() {
|
|
791
|
-
if (!this.badgeEl) return;
|
|
792
|
-
const show = !this.open && this.unseen > 0;
|
|
793
|
-
this.badgeEl.hidden = !show;
|
|
794
|
-
this.badgeEl.textContent = this.unseen > 9 ? "9+" : String(this.unseen);
|
|
1155
|
+
this.updateClasses();
|
|
795
1156
|
}
|
|
796
1157
|
// ── Composer ──────────────────────────────────────────────────────────────────────────────────
|
|
797
1158
|
autoGrow() {
|
|
798
1159
|
const ta = this.textareaEl;
|
|
799
1160
|
ta.style.height = "auto";
|
|
800
|
-
ta.style.height = `${Math.min(ta.scrollHeight,
|
|
1161
|
+
ta.style.height = `${Math.min(ta.scrollHeight, 108)}px`;
|
|
801
1162
|
}
|
|
802
1163
|
doSend() {
|
|
803
1164
|
const text = this.textareaEl.value;
|
|
@@ -810,15 +1171,63 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
810
1171
|
this.imagePreviewCache.set(f.name, URL.createObjectURL(f));
|
|
811
1172
|
}
|
|
812
1173
|
}
|
|
813
|
-
this.
|
|
1174
|
+
this.away = false;
|
|
1175
|
+
this.emojiOpen = false;
|
|
814
1176
|
void this.client?.sendMessage(text, files.length > 0 ? files : void 0);
|
|
815
1177
|
this.textareaEl.value = "";
|
|
816
1178
|
this.textareaEl.style.height = "auto";
|
|
1179
|
+
this.updateClasses();
|
|
1180
|
+
}
|
|
1181
|
+
sendChip(text) {
|
|
1182
|
+
this.away = false;
|
|
1183
|
+
this.emojiOpen = false;
|
|
1184
|
+
void this.client?.sendMessage(text);
|
|
1185
|
+
this.updateClasses();
|
|
1186
|
+
}
|
|
1187
|
+
bindChips() {
|
|
1188
|
+
const sugg = this.suggestions;
|
|
1189
|
+
this.bodyEl.querySelectorAll(".chip[data-chip]").forEach((btn) => {
|
|
1190
|
+
const i = Number(btn.getAttribute("data-chip"));
|
|
1191
|
+
const text = sugg[i];
|
|
1192
|
+
if (text) btn.addEventListener("click", () => this.sendChip(text));
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
// ── Emoji ─────────────────────────────────────────────────────────────────────────────────────
|
|
1196
|
+
toggleEmoji() {
|
|
1197
|
+
this.emojiOpen = !this.emojiOpen;
|
|
1198
|
+
this.emojiQuery = "";
|
|
1199
|
+
this.emojiSearchEl.value = "";
|
|
1200
|
+
this.renderEmoji();
|
|
1201
|
+
this.updateClasses();
|
|
1202
|
+
if (this.emojiOpen) setTimeout(() => this.emojiSearchEl.focus(), 60);
|
|
1203
|
+
}
|
|
1204
|
+
closeEmoji() {
|
|
1205
|
+
this.emojiOpen = false;
|
|
1206
|
+
this.emojiQuery = "";
|
|
1207
|
+
this.updateClasses();
|
|
1208
|
+
}
|
|
1209
|
+
renderEmoji() {
|
|
1210
|
+
const groups = emojiGroups(this.emojiQuery);
|
|
1211
|
+
let html = groups.map((g) => `
|
|
1212
|
+
<div class="emoji-grp"><span class="emoji-grp-name">${escapeHtml(g.name)}</span>
|
|
1213
|
+
<div class="emoji-grid">${g.items.map((e) => `<button type="button" title="${this.escapeAttr(e.name)}" aria-label="${this.escapeAttr(e.name)}" data-ch="${this.escapeAttr(e.ch)}">${e.ch}</button>`).join("")}</div>
|
|
1214
|
+
</div>`).join("");
|
|
1215
|
+
if (groups.length === 0) html = '<span class="emoji-empty" style="display:flex"><span class="h">No emoji match that</span><span class="s">Try a shorter word, like smile or call.</span></span>';
|
|
1216
|
+
this.emojiListEl.innerHTML = html;
|
|
1217
|
+
this.emojiListEl.querySelectorAll("button[data-ch]").forEach((btn) => {
|
|
1218
|
+
btn.addEventListener("click", () => {
|
|
1219
|
+
this.textareaEl.value += btn.getAttribute("data-ch") ?? "";
|
|
1220
|
+
this.emojiOpen = false;
|
|
1221
|
+
this.autoGrow();
|
|
1222
|
+
this.updateClasses();
|
|
1223
|
+
this.textareaEl.focus();
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
817
1226
|
}
|
|
818
1227
|
// ── Bubble rendering ──────────────────────────────────────────────────────────────────────────
|
|
819
|
-
|
|
1228
|
+
turnHtml(m, isLastBot) {
|
|
1229
|
+
const role = m.role === "user" ? "user" : m.role === "human" ? "human" : "bot";
|
|
820
1230
|
const atts = (m.attachments ?? []).map((a) => this.renderAttachment(a)).join("");
|
|
821
|
-
const label = m.role === "human" ? '<div class="human-label">Support</div>' : "";
|
|
822
1231
|
let content;
|
|
823
1232
|
if (m.role === "assistant") {
|
|
824
1233
|
let rendered = m.streaming ? void 0 : this.mdCache.get(m.id);
|
|
@@ -826,30 +1235,32 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
826
1235
|
rendered = renderMarkdown(m.text || "");
|
|
827
1236
|
if (!m.streaming) this.mdCache.set(m.id, rendered);
|
|
828
1237
|
}
|
|
829
|
-
content = `<
|
|
1238
|
+
content = `<span class="b">${atts}<span class="md">${rendered}</span></span>`;
|
|
830
1239
|
} else {
|
|
831
|
-
|
|
1240
|
+
const label = m.role === "human" ? '<div class="human-label">Support</div>' : "";
|
|
1241
|
+
content = `<span class="b">${label}${atts}${escapeHtml(m.text)}</span>`;
|
|
832
1242
|
}
|
|
833
|
-
const
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
1243
|
+
const src = m.sources && m.sources.length > 0 ? `<span class="src">${ICON.book}<span class="t">${escapeHtml("From " + (m.sources[0]?.title ?? ""))}</span></span>` : "";
|
|
1244
|
+
const byline = role === "bot" && isLastBot ? `<span class="byline">${escapeHtml((this.agentName ?? "OyeHello") + " \xB7 AI agent \xB7 " + this.relTime(m.id))}</span>` : "";
|
|
1245
|
+
return `<span class="turn ${role}">${content}${src}${byline}</span>`;
|
|
1246
|
+
}
|
|
1247
|
+
relTime(id) {
|
|
1248
|
+
const t = this.receivedAt.get(id);
|
|
1249
|
+
if (!t) return "just now";
|
|
1250
|
+
const mins = Math.floor((Date.now() - t) / 6e4);
|
|
1251
|
+
return mins <= 0 ? "just now" : `${mins} min ago`;
|
|
837
1252
|
}
|
|
838
1253
|
renderAttachment(a) {
|
|
839
1254
|
if (a.contentType.startsWith("image/")) {
|
|
840
1255
|
const cached = (a.name ? this.imagePreviewCache.get(a.name) : void 0) ?? this.resolvedUrlCache.get(a.storageRef);
|
|
841
1256
|
const srcAttr = cached ? ` src="${cached}"` : "";
|
|
842
|
-
return `<img class="att-thumb" data-oc-ref="${
|
|
1257
|
+
return `<img class="att-thumb" data-oc-ref="${this.escapeAttr(a.storageRef)}"${srcAttr} alt="${this.escapeAttr(a.name ?? "image")}"/>`;
|
|
843
1258
|
}
|
|
844
1259
|
return `<div class="att-chip">\u{1F4CE} ${escapeHtml(a.name ?? a.contentType)}</div>`;
|
|
845
1260
|
}
|
|
846
|
-
renderSource(s) {
|
|
847
|
-
return `<div class="src-chip" title="${escapeHtml(s.snippet)}">${escapeHtml(s.title)}</div>`;
|
|
848
|
-
}
|
|
849
1261
|
hydrateAttachmentImages() {
|
|
850
1262
|
if (!this.client) return;
|
|
851
|
-
|
|
852
|
-
imgs.forEach((img) => {
|
|
1263
|
+
this.shadowRoot.querySelectorAll("img.att-thumb[data-oc-ref]").forEach((img) => {
|
|
853
1264
|
if (img.hasAttribute("src")) return;
|
|
854
1265
|
const ref = img.getAttribute("data-oc-ref");
|
|
855
1266
|
if (!ref) return;
|
|
@@ -860,6 +1271,9 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
860
1271
|
});
|
|
861
1272
|
});
|
|
862
1273
|
}
|
|
1274
|
+
escapeAttr(s) {
|
|
1275
|
+
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
1276
|
+
}
|
|
863
1277
|
};
|
|
864
1278
|
function escapeHtml(s) {
|
|
865
1279
|
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|