@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.global.js
CHANGED
|
@@ -160,7 +160,7 @@ var OyehelloChat = (function (exports) {
|
|
|
160
160
|
seenTurnIds.add(ev.turnId);
|
|
161
161
|
const m2 = streamingId ? messages.find((x2) => x2.id === streamingId) : void 0;
|
|
162
162
|
if (m2) {
|
|
163
|
-
m2.text = ev.text;
|
|
163
|
+
m2.text = ev.text || m2.text;
|
|
164
164
|
m2.streaming = false;
|
|
165
165
|
m2.turnId = ev.turnId;
|
|
166
166
|
} else {
|
|
@@ -3285,132 +3285,369 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3285
3285
|
// src/styles.ts
|
|
3286
3286
|
var STYLES = `
|
|
3287
3287
|
:host {
|
|
3288
|
-
--oc-accent
|
|
3289
|
-
--oc-
|
|
3290
|
-
--oc-
|
|
3291
|
-
--oc-
|
|
3292
|
-
--oc-
|
|
3293
|
-
--oc-
|
|
3294
|
-
--oc-
|
|
3295
|
-
|
|
3296
|
-
|
|
3288
|
+
/* accent (consumer-overridable via --oc-accent) + its derivations */
|
|
3289
|
+
--oc-ac: var(--oc-accent, #096DD9);
|
|
3290
|
+
--oc-btn-rim: color-mix(in oklab, var(--oc-ac) 70%, black);
|
|
3291
|
+
--oc-btn-edge: 0 6px 16px -10px color-mix(in oklab, var(--oc-ac) 55%, transparent);
|
|
3292
|
+
--oc-danger: #D92D20;
|
|
3293
|
+
--oc-ease: cubic-bezier(0.16,1,0.3,1);
|
|
3294
|
+
--oc-bounce: cubic-bezier(0.18,0.89,0.32,1.13);
|
|
3295
|
+
--oc-mono: 'Geist Mono', ui-monospace, monospace;
|
|
3296
|
+
--oc-r-full: 999px;
|
|
3297
|
+
|
|
3298
|
+
/* light palette (base) */
|
|
3299
|
+
--oc-bg: #FFFFFF; --oc-bg-sunk: #F6F8FB;
|
|
3300
|
+
--oc-fg: #0A0D12; --oc-fg-muted: rgba(10,13,18,0.66); --oc-fg-dim: rgba(10,13,18,0.44);
|
|
3301
|
+
--oc-border: rgba(9,13,20,0.10);
|
|
3302
|
+
--oc-accent-text: color-mix(in oklab, var(--oc-ac) 88%, black);
|
|
3303
|
+
--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);
|
|
3304
|
+
--oc-field-bg: #FFFFFF; --oc-field-border: rgba(9,13,20,0.22);
|
|
3305
|
+
--oc-glass-border: rgba(9,13,20,0.12);
|
|
3306
|
+
|
|
3307
|
+
position: fixed; right: 24px; bottom: 24px; z-index: 2147483000;
|
|
3308
|
+
font-family: 'Geist', system-ui, -apple-system, 'Helvetica Neue', sans-serif;
|
|
3309
|
+
-webkit-font-smoothing: antialiased;
|
|
3310
|
+
}
|
|
3311
|
+
@media (prefers-color-scheme: dark) {
|
|
3312
|
+
:host(:not([theme="light"])) {
|
|
3313
|
+
--oc-bg: #000000; --oc-bg-sunk: #000000;
|
|
3314
|
+
--oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
|
|
3315
|
+
--oc-border: rgba(255,255,255,0.09);
|
|
3316
|
+
--oc-accent-text: color-mix(in oklab, var(--oc-ac) 62%, white);
|
|
3317
|
+
--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);
|
|
3318
|
+
--oc-field-bg: rgba(255,255,255,0.045); --oc-field-border: rgba(255,255,255,0.20);
|
|
3319
|
+
--oc-glass-border: rgba(255,255,255,0.12);
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
:host([theme="dark"]) {
|
|
3323
|
+
--oc-bg: #000000; --oc-bg-sunk: #000000;
|
|
3324
|
+
--oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
|
|
3325
|
+
--oc-border: rgba(255,255,255,0.09);
|
|
3326
|
+
--oc-accent-text: color-mix(in oklab, var(--oc-ac) 62%, white);
|
|
3327
|
+
--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);
|
|
3328
|
+
--oc-field-bg: rgba(255,255,255,0.045); --oc-field-border: rgba(255,255,255,0.20);
|
|
3329
|
+
--oc-glass-border: rgba(255,255,255,0.12);
|
|
3297
3330
|
}
|
|
3298
3331
|
* { box-sizing: border-box; }
|
|
3332
|
+
button { font-family: inherit; }
|
|
3299
3333
|
|
|
3300
|
-
|
|
3334
|
+
.oc { display: flex; flex-direction: column; align-items: flex-end; gap: 14px; }
|
|
3335
|
+
|
|
3336
|
+
/* \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 */
|
|
3301
3337
|
.launcher {
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3338
|
+
position: relative; flex: none; width: 46px; height: 46px; border-radius: var(--oc-r-full);
|
|
3339
|
+
border: 1px solid var(--oc-btn-rim); background: var(--oc-ac);
|
|
3340
|
+
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);
|
|
3341
|
+
color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer;
|
|
3342
|
+
transition: transform .26s var(--oc-bounce), filter .2s var(--oc-ease); align-self: flex-end;
|
|
3305
3343
|
}
|
|
3306
3344
|
.launcher:hover { transform: scale(1.06); }
|
|
3307
|
-
.launcher
|
|
3308
|
-
.
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3345
|
+
.launcher:active { transform: scale(0.93); }
|
|
3346
|
+
.ring { pointer-events: none; position: absolute; inset: -1px; border-radius: var(--oc-r-full); border: 2px solid var(--oc-ac); opacity: 0; }
|
|
3347
|
+
.oc.ring:not(.open) .ring { animation: w-ring 2.4s ease-out 1.1s 3; }
|
|
3348
|
+
.ic-chat, .ic-close { position: absolute; display: flex; align-items: center; justify-content: center; transition: all .3s var(--oc-bounce); }
|
|
3349
|
+
.ic-chat { width: 28px; height: 28px; opacity: 1; transform: rotate(0) scale(1); }
|
|
3350
|
+
.ic-close { opacity: 0; transform: rotate(90deg) scale(0.6); }
|
|
3351
|
+
.oc.open .ic-chat { opacity: 0; transform: rotate(-90deg) scale(0.6); }
|
|
3352
|
+
.oc.open .ic-close { opacity: 1; transform: rotate(0) scale(1); }
|
|
3353
|
+
.badge {
|
|
3354
|
+
position: absolute; top: -2px; right: -2px; min-width: 17px; height: 17px; padding: 0 5px;
|
|
3355
|
+
border-radius: var(--oc-r-full); border: 2px solid var(--oc-bg-sunk); background: var(--oc-danger);
|
|
3356
|
+
color: #fff; font-family: var(--oc-mono); font-size: 10px; font-weight: 700;
|
|
3357
|
+
display: flex; align-items: center; justify-content: center; opacity: 0; transform: scale(0.4);
|
|
3358
|
+
transition: all .26s var(--oc-bounce);
|
|
3312
3359
|
}
|
|
3360
|
+
.oc.unread:not(.open) .badge { opacity: 1; transform: scale(1); }
|
|
3313
3361
|
|
|
3314
|
-
/*
|
|
3362
|
+
/* \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 */
|
|
3363
|
+
.teaser {
|
|
3364
|
+
position: absolute; right: 0; bottom: 60px; display: none; align-items: center; gap: 11px;
|
|
3365
|
+
opacity: 0; transform: translateY(8px) scale(0.94); pointer-events: none;
|
|
3366
|
+
transition: opacity .3s var(--oc-ease), transform .38s var(--oc-bounce);
|
|
3367
|
+
}
|
|
3368
|
+
.oc.has-teaser .teaser { display: flex; }
|
|
3369
|
+
.oc.teaser:not(.open) .teaser { opacity: 1; transform: none; pointer-events: auto; }
|
|
3370
|
+
.teaser-btn {
|
|
3371
|
+
max-width: 250px; display: flex; align-items: center; gap: 10px; padding: 11px 15px;
|
|
3372
|
+
border-radius: 16px 16px 5px 16px; border: 1px solid var(--oc-glass-border); background: var(--oc-bg);
|
|
3373
|
+
box-shadow: 0 10px 30px -14px rgba(0,0,0,0.5); color: var(--oc-fg); font-size: 13.5px; line-height: 1.45;
|
|
3374
|
+
text-align: left; cursor: pointer; transition: transform .2s var(--oc-ease);
|
|
3375
|
+
}
|
|
3376
|
+
.teaser-btn:hover { transform: translateY(-1px); }
|
|
3377
|
+
.teaser-btn .logo { flex: none; width: 24px; height: 24px; border-radius: 6px; overflow: hidden; color: var(--oc-ac); }
|
|
3378
|
+
.teaser-x {
|
|
3379
|
+
flex: none; width: 22px; height: 22px; border-radius: var(--oc-r-full); border: 1px solid var(--oc-border);
|
|
3380
|
+
background: var(--oc-bg); color: var(--oc-fg-dim); display: flex; align-items: center; justify-content: center; cursor: pointer;
|
|
3381
|
+
}
|
|
3382
|
+
.teaser-x:hover { color: var(--oc-fg); }
|
|
3383
|
+
|
|
3384
|
+
/* \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 */
|
|
3315
3385
|
.panel {
|
|
3316
|
-
position:
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3386
|
+
position: relative; width: min(384px, calc(100vw - 40px)); height: min(560px, calc(100vh - 108px));
|
|
3387
|
+
display: flex; flex-direction: column; border-radius: 20px; border: 1px solid var(--oc-glass-border);
|
|
3388
|
+
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);
|
|
3389
|
+
overflow: hidden; transform-origin: bottom right;
|
|
3390
|
+
opacity: 0; transform: translateY(16px) scale(0.94); pointer-events: none; visibility: hidden;
|
|
3391
|
+
transition: opacity .24s var(--oc-ease), transform .34s var(--oc-bounce), visibility .34s, width .3s var(--oc-ease), height .3s var(--oc-ease);
|
|
3321
3392
|
}
|
|
3322
|
-
.panel
|
|
3323
|
-
.
|
|
3393
|
+
.oc.open .panel { opacity: 1; transform: none; pointer-events: auto; visibility: visible; }
|
|
3394
|
+
.oc.max .panel { width: min(720px, calc(100vw - 40px)); height: calc(100vh - 108px); }
|
|
3324
3395
|
|
|
3325
|
-
.hd { display: flex; align-items: center; gap:
|
|
3326
|
-
.hd
|
|
3327
|
-
.hd .
|
|
3328
|
-
.hd
|
|
3329
|
-
.hd .
|
|
3396
|
+
.hd { position: relative; flex: none; display: flex; align-items: center; gap: 12px; padding: 13px 14px 12px; background: var(--oc-ac); color: #fff; overflow: hidden; }
|
|
3397
|
+
.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; }
|
|
3398
|
+
.hd .logo { position: relative; flex: none; width: 32px; height: 32px; border-radius: 7px; overflow: hidden; color: rgba(255,255,255,0.22); }
|
|
3399
|
+
.hd-txt { position: relative; flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
|
|
3400
|
+
.hd-title { font-size: 14.5px; font-weight: 600; letter-spacing: -0.015em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
3401
|
+
.hd-status { display: flex; align-items: center; gap: 6px; font-size: 11.5px; opacity: 0.9; }
|
|
3402
|
+
.hd-status .dot { width: 6px; height: 6px; border-radius: var(--oc-r-full); background: #7BE3A6; flex: none; }
|
|
3403
|
+
.hd-status .t { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
3404
|
+
.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); }
|
|
3405
|
+
.hd-btn:hover { background: rgba(255,255,255,0.28); }
|
|
3406
|
+
.hd-btn .i { position: absolute; display: flex; transition: opacity .2s var(--oc-ease); }
|
|
3407
|
+
.i-expand { opacity: 1; } .i-shrink { opacity: 0; }
|
|
3408
|
+
.oc.max .i-expand { opacity: 0; } .oc.max .i-shrink { opacity: 1; }
|
|
3330
3409
|
|
|
3331
|
-
.
|
|
3332
|
-
.err { padding:
|
|
3333
|
-
.err .retry { padding: 3px 10px; font-size: 11px; background: #b91c1c; color: #fff; border: none; border-radius: 6px; cursor: pointer; }
|
|
3410
|
+
.err { flex: none; display: flex; align-items: center; gap: 8px; padding: 8px 16px; font-size: 12px; color: #fff; background: var(--oc-danger); }
|
|
3411
|
+
.err .retry { padding: 3px 10px; font-size: 11px; background: rgba(255,255,255,0.2); color: #fff; border: none; border-radius: 6px; cursor: pointer; }
|
|
3334
3412
|
|
|
3335
|
-
/* Message list */
|
|
3336
|
-
.body { flex: 1;
|
|
3337
|
-
.
|
|
3338
|
-
.
|
|
3339
|
-
.b
|
|
3340
|
-
.
|
|
3341
|
-
.
|
|
3413
|
+
/* \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 */
|
|
3414
|
+
.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; }
|
|
3415
|
+
.turn { display: flex; flex-direction: column; gap: 6px; animation: w-in .42s var(--oc-ease) both; }
|
|
3416
|
+
.turn.bot { align-items: flex-start; } .turn.user { align-items: flex-end; }
|
|
3417
|
+
.b { font-size: 13.5px; line-height: 1.55; text-wrap: pretty; word-break: break-word; }
|
|
3418
|
+
.turn.bot .b { max-width: 86%; padding: 13px 15px; border-radius: 20px; background: var(--oc-tint-2); color: var(--oc-fg); }
|
|
3419
|
+
.turn.user .b { max-width: 82%; padding: 11px 15px; border-radius: var(--oc-r-full); background: var(--oc-ac); color: #fff; }
|
|
3420
|
+
.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); }
|
|
3421
|
+
.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%; }
|
|
3422
|
+
.src .t { min-width: 0; font-size: 11.5px; color: var(--oc-fg-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3423
|
+
.byline { display: flex; align-items: center; gap: 6px; padding: 0 4px; font-size: 11.5px; color: var(--oc-fg-dim); }
|
|
3342
3424
|
.human-label { font-size: 11px; font-weight: 600; opacity: .7; margin-bottom: 2px; }
|
|
3343
3425
|
|
|
3344
|
-
/*
|
|
3345
|
-
.md > :first-child { margin-top: 0; }
|
|
3346
|
-
.md
|
|
3347
|
-
.md
|
|
3348
|
-
.md
|
|
3349
|
-
.md
|
|
3350
|
-
.md
|
|
3351
|
-
.
|
|
3352
|
-
.md code { font-family: ui-monospace, monospace; font-size: 12.5px; background: rgba(0,0,0,.07); padding: 1px 5px; border-radius: 5px; }
|
|
3353
|
-
.md pre { background: #0f172a; color: #e2e8f0; padding: 10px 12px; border-radius: 8px; overflow-x: auto; margin: 0 0 8px; }
|
|
3354
|
-
.md pre code { background: none; padding: 0; color: inherit; }
|
|
3355
|
-
.md blockquote { margin: 0 0 8px; padding-left: 10px; border-left: 3px solid var(--oc-border); color: var(--oc-muted); }
|
|
3426
|
+
/* markdown inside a bot bubble */
|
|
3427
|
+
.md > :first-child { margin-top: 0; } .md > :last-child { margin-bottom: 0; }
|
|
3428
|
+
.md p { margin: 0 0 8px; } .md ul, .md ol { margin: 0 0 8px; padding-left: 20px; } .md li { margin: 2px 0; }
|
|
3429
|
+
.md a { color: var(--oc-accent-text); text-decoration: underline; text-underline-offset: 2px; }
|
|
3430
|
+
.md code { font-family: var(--oc-mono); font-size: 12.5px; background: var(--oc-tint-3); padding: 1px 5px; border-radius: 5px; }
|
|
3431
|
+
.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; }
|
|
3432
|
+
.md pre code { background: none; padding: 0; }
|
|
3433
|
+
.md blockquote { margin: 0 0 8px; padding-left: 10px; border-left: 3px solid var(--oc-border); color: var(--oc-fg-muted); }
|
|
3356
3434
|
.md h1, .md h2, .md h3, .md h4 { margin: 8px 0 6px; font-size: 15px; }
|
|
3357
3435
|
.md table { border-collapse: collapse; font-size: 13px; margin: 0 0 8px; display: block; overflow-x: auto; }
|
|
3358
3436
|
.md th, .md td { border: 1px solid var(--oc-border); padding: 4px 8px; }
|
|
3359
|
-
.md hr { border: none; border-top: 1px solid var(--oc-border); margin: 8px 0; }
|
|
3360
|
-
|
|
3361
|
-
.src-row { align-self: flex-start; display: flex; flex-wrap: wrap; gap: 6px; max-width: 85%; }
|
|
3362
|
-
.src-chip { font-size: 11px; background: rgba(0,0,0,.06); color: #374151; border-radius: 6px; padding: 2px 8px; cursor: default; }
|
|
3363
3437
|
.att-thumb { display: block; max-width: 180px; max-height: 180px; border-radius: 8px; margin-bottom: 6px; }
|
|
3364
|
-
.att-chip { display: inline-block; font-size: 12px; background:
|
|
3365
|
-
.
|
|
3438
|
+
.att-chip { display: inline-block; font-size: 12px; background: var(--oc-tint-3); border-radius: 6px; padding: 2px 8px; margin: 0 0 6px; }
|
|
3439
|
+
.turn.user .att-chip { background: rgba(255,255,255,0.25); }
|
|
3366
3440
|
|
|
3367
|
-
|
|
3368
|
-
.
|
|
3369
|
-
|
|
3370
|
-
background: var(--oc-accent); color: #fff; border: none; cursor: pointer;
|
|
3371
|
-
font-size: 12px; font-weight: 600; padding: 6px 12px; border-radius: 999px;
|
|
3372
|
-
box-shadow: 0 4px 14px rgba(0,0,0,.25); display: flex; align-items: center; gap: 6px;
|
|
3373
|
-
}
|
|
3374
|
-
.pill[hidden] { display: none; }
|
|
3375
|
-
.pill svg { width: 13px; height: 13px; }
|
|
3441
|
+
.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; }
|
|
3442
|
+
.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; }
|
|
3443
|
+
.typing span:nth-child(2) { animation-delay: .15s; } .typing span:nth-child(3) { animation-delay: .3s; }
|
|
3376
3444
|
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
}
|
|
3383
|
-
|
|
3384
|
-
.
|
|
3385
|
-
.
|
|
3445
|
+
.chips { display: flex; flex-wrap: wrap; gap: 7px; padding-top: 2px; animation: w-in .42s var(--oc-ease) both; animation-delay: .3s; }
|
|
3446
|
+
.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); }
|
|
3447
|
+
.chip:hover { background: color-mix(in oklab, var(--oc-ac) 18%, transparent); }
|
|
3448
|
+
|
|
3449
|
+
/* \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 */
|
|
3450
|
+
.jump-wrap { position: relative; flex: none; height: 0; z-index: 2; display: flex; justify-content: center; }
|
|
3451
|
+
.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); }
|
|
3452
|
+
.oc.away .jump { opacity: 1; transform: none; pointer-events: auto; }
|
|
3453
|
+
.jump:hover { color: var(--oc-fg); border-color: var(--oc-fg-dim); }
|
|
3454
|
+
|
|
3455
|
+
/* \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 */
|
|
3456
|
+
.composer { flex: none; display: flex; flex-direction: column; gap: 9px; padding: 12px 14px 13px; background: var(--oc-bg); }
|
|
3457
|
+
.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); }
|
|
3458
|
+
.oc.focus .field { border-color: var(--oc-ac); }
|
|
3459
|
+
.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; }
|
|
3460
|
+
.input::placeholder { color: var(--oc-fg-dim); }
|
|
3461
|
+
.acts { display: flex; align-items: center; gap: 16px; }
|
|
3462
|
+
.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); }
|
|
3463
|
+
.act:hover { color: var(--oc-fg-muted); }
|
|
3464
|
+
.oc.emoji .act.emoji-btn { color: var(--oc-accent-text); }
|
|
3465
|
+
.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); }
|
|
3466
|
+
.oc.ready .send { border-color: var(--oc-btn-rim); background: var(--oc-ac); color: #fff; box-shadow: var(--oc-btn-edge); cursor: pointer; }
|
|
3467
|
+
.oc.ready .send:hover { filter: brightness(1.1); }
|
|
3468
|
+
.send:active { transform: scale(0.92); }
|
|
3469
|
+
|
|
3470
|
+
.foot { position: relative; display: none; text-align: center; font-size: 11.5px; line-height: 1.5; color: var(--oc-fg-dim); }
|
|
3471
|
+
.oc.has-privacy .foot { display: block; }
|
|
3472
|
+
.foot .priv { color: var(--oc-fg-muted); text-decoration: underline; text-underline-offset: 2px; cursor: default; }
|
|
3473
|
+
.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); }
|
|
3474
|
+
.oc.max .foot .tip { right: 188px; }
|
|
3475
|
+
.foot:hover .tip { opacity: 1; }
|
|
3476
|
+
.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); }
|
|
3477
|
+
.foot:hover .tip-card { transform: none; }
|
|
3478
|
+
.foot .tip-card a { color: var(--oc-fg); text-decoration: underline; text-underline-offset: 2px; }
|
|
3479
|
+
|
|
3480
|
+
/* \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 */
|
|
3481
|
+
.emoji-scrim { position: absolute; inset: 0; z-index: 3; display: none; }
|
|
3482
|
+
.oc.emoji .emoji-scrim { display: block; }
|
|
3483
|
+
.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; }
|
|
3484
|
+
.oc.emoji .emoji-panel { opacity: 1; transform: none; pointer-events: auto; visibility: visible; }
|
|
3485
|
+
.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); }
|
|
3486
|
+
.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; }
|
|
3487
|
+
.emoji-search input::placeholder { color: var(--oc-fg-dim); }
|
|
3488
|
+
.emoji-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; padding: 12px 14px 16px; }
|
|
3489
|
+
.emoji-grp { display: flex; flex-direction: column; gap: 6px; }
|
|
3490
|
+
.emoji-grp + .emoji-grp { padding-top: 8px; }
|
|
3491
|
+
.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); }
|
|
3492
|
+
.emoji-grid { display: grid; grid-template-columns: repeat(7, minmax(0,1fr)); gap: 2px; }
|
|
3493
|
+
.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); }
|
|
3494
|
+
.emoji-grid button:hover { background: var(--oc-tint-3); }
|
|
3495
|
+
.emoji-empty { display: none; flex-direction: column; align-items: center; gap: 5px; padding: 26px 10px; text-align: center; }
|
|
3496
|
+
.emoji-empty .h { font-size: 14px; color: var(--oc-fg); } .emoji-empty .s { font-size: 12.5px; color: var(--oc-fg-dim); }
|
|
3386
3497
|
|
|
3387
3498
|
@media (max-width: 480px) {
|
|
3388
3499
|
:host { right: 0; bottom: 0; }
|
|
3389
|
-
.
|
|
3390
|
-
.
|
|
3391
|
-
.
|
|
3500
|
+
.oc { padding: 0 16px 16px; }
|
|
3501
|
+
.panel { position: fixed; inset: 0; width: 100vw; height: 100dvh; border-radius: 0; }
|
|
3502
|
+
.oc.max .panel { width: 100vw; height: 100dvh; }
|
|
3392
3503
|
}
|
|
3393
3504
|
@media (prefers-reduced-motion: reduce) {
|
|
3394
|
-
.launcher, .panel { transition: none; }
|
|
3395
|
-
.body { scroll-behavior: auto; }
|
|
3505
|
+
.launcher, .panel, .teaser, .turn, .jump, .emoji-panel { transition: none; animation: none; }
|
|
3506
|
+
.ring { animation: none; } .body { scroll-behavior: auto; }
|
|
3396
3507
|
}
|
|
3508
|
+
|
|
3509
|
+
@keyframes w-in { from { opacity: 0; transform: translateY(10px) scale(0.97); } to { opacity: 1; transform: none; } }
|
|
3510
|
+
@keyframes w-dot { 0%,60%,100% { transform: translateY(0); opacity: .45; } 30% { transform: translateY(-4px); opacity: 1; } }
|
|
3511
|
+
@keyframes w-ring { 0% { transform: scale(1); opacity: .5; } 70%,100% { transform: scale(1.7); opacity: 0; } }
|
|
3397
3512
|
`;
|
|
3398
3513
|
|
|
3514
|
+
// src/icons.ts
|
|
3515
|
+
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>`;
|
|
3516
|
+
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>`;
|
|
3517
|
+
var ICON = {
|
|
3518
|
+
close: (size = 19) => stroke(2.4, '<path d="M18 6 6 18M6 6l12 12"/>', size),
|
|
3519
|
+
minus: stroke(2, '<path d="M6 12h12"/>', 15),
|
|
3520
|
+
expand: stroke(2, '<path d="M15 4h5v5M20 4l-6.5 6.5M9 20H4v-5M4 20l6.5-6.5"/>', 15),
|
|
3521
|
+
shrink: stroke(2, '<path d="M20 9h-5V4M15 9l5.5-5.5M4 15h5v5M9 15l-5.5 5.5"/>', 15),
|
|
3522
|
+
chevronDown: stroke(2.1, '<path d="m6 9 6 6 6-6"/>', 16),
|
|
3523
|
+
send: stroke(2.2, '<path d="M12 19V5M6 11l6-6 6 6"/>', 16),
|
|
3524
|
+
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),
|
|
3525
|
+
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),
|
|
3526
|
+
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),
|
|
3527
|
+
search: stroke(1.9, '<circle cx="11" cy="11" r="7.5"/><path d="m21 21-4.3-4.3"/>', 17),
|
|
3528
|
+
clear: stroke(2.2, '<path d="M18 6 6 18M6 6l12 12"/>', 15),
|
|
3529
|
+
// Two-page "source" glyph for citation chips.
|
|
3530
|
+
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>`,
|
|
3531
|
+
teaserDismiss: stroke(2.4, '<path d="M18 6 6 18M6 6l12 12"/>', 12)
|
|
3532
|
+
};
|
|
3533
|
+
|
|
3534
|
+
// src/emoji.ts
|
|
3535
|
+
var CATS = [
|
|
3536
|
+
{ name: "Frequently used", items: [
|
|
3537
|
+
["\u{1F44D}", "thumbs up"],
|
|
3538
|
+
["\u{1F64F}", "thanks"],
|
|
3539
|
+
["\u{1F44B}", "wave"],
|
|
3540
|
+
["\u2705", "done"],
|
|
3541
|
+
["\u{1F4DE}", "call"],
|
|
3542
|
+
["\u{1F60A}", "smile"],
|
|
3543
|
+
["\u2764\uFE0F", "heart"]
|
|
3544
|
+
] },
|
|
3545
|
+
{ name: "Smileys and emotion", items: [
|
|
3546
|
+
["\u{1F600}", "grinning"],
|
|
3547
|
+
["\u{1F603}", "smiley"],
|
|
3548
|
+
["\u{1F604}", "grin"],
|
|
3549
|
+
["\u{1F601}", "beaming"],
|
|
3550
|
+
["\u{1F606}", "squinting"],
|
|
3551
|
+
["\u{1F605}", "relief"],
|
|
3552
|
+
["\u{1F923}", "rolling"],
|
|
3553
|
+
["\u{1F602}", "tears of joy"],
|
|
3554
|
+
["\u{1F642}", "slightly smiling"],
|
|
3555
|
+
["\u{1F643}", "upside down"],
|
|
3556
|
+
["\u{1F609}", "wink"],
|
|
3557
|
+
["\u{1F607}", "halo"],
|
|
3558
|
+
["\u{1F970}", "smiling hearts"],
|
|
3559
|
+
["\u{1F60D}", "heart eyes"],
|
|
3560
|
+
["\u{1F618}", "kiss"],
|
|
3561
|
+
["\u{1F60B}", "yum"],
|
|
3562
|
+
["\u{1F914}", "thinking"],
|
|
3563
|
+
["\u{1F928}", "raised brow"],
|
|
3564
|
+
["\u{1F610}", "neutral"],
|
|
3565
|
+
["\u{1F644}", "eye roll"],
|
|
3566
|
+
["\u{1F62C}", "grimace"],
|
|
3567
|
+
["\u{1F625}", "sad"],
|
|
3568
|
+
["\u{1F622}", "crying"],
|
|
3569
|
+
["\u{1F62D}", "sobbing"],
|
|
3570
|
+
["\u{1F620}", "angry"],
|
|
3571
|
+
["\u{1F633}", "flushed"],
|
|
3572
|
+
["\u{1F929}", "star struck"],
|
|
3573
|
+
["\u{1F634}", "sleeping"]
|
|
3574
|
+
] },
|
|
3575
|
+
{ name: "Gestures", items: [
|
|
3576
|
+
["\u{1F44E}", "thumbs down"],
|
|
3577
|
+
["\u{1F44F}", "clap"],
|
|
3578
|
+
["\u{1F64C}", "raised hands"],
|
|
3579
|
+
["\u{1F91D}", "handshake"],
|
|
3580
|
+
["\u{1F44C}", "ok hand"],
|
|
3581
|
+
["\u270C\uFE0F", "victory"],
|
|
3582
|
+
["\u{1F4AA}", "strong"],
|
|
3583
|
+
["\u{1F440}", "eyes"],
|
|
3584
|
+
["\u{1F9E0}", "brain"],
|
|
3585
|
+
["\u{1F91F}", "love you"],
|
|
3586
|
+
["\u261D\uFE0F", "point up"],
|
|
3587
|
+
["\u{1F447}", "point down"],
|
|
3588
|
+
["\u{1F91A}", "raised back"],
|
|
3589
|
+
["\u{1F91E}", "fingers crossed"]
|
|
3590
|
+
] },
|
|
3591
|
+
{ name: "Work and travel", items: [
|
|
3592
|
+
["\u{1F4C5}", "calendar"],
|
|
3593
|
+
["\u{1F4C4}", "document"],
|
|
3594
|
+
["\u{1F4B0}", "money"],
|
|
3595
|
+
["\u{1F4CA}", "chart"],
|
|
3596
|
+
["\u{1F4E6}", "package"],
|
|
3597
|
+
["\u{1F3E5}", "hospital"],
|
|
3598
|
+
["\u{1F3E6}", "bank"],
|
|
3599
|
+
["\u{1F69A}", "truck"],
|
|
3600
|
+
["\u2708\uFE0F", "plane"],
|
|
3601
|
+
["\u{1F4F1}", "phone"],
|
|
3602
|
+
["\u{1F4BB}", "laptop"],
|
|
3603
|
+
["\u{1F50D}", "search"],
|
|
3604
|
+
["\u{1F4CC}", "pin"],
|
|
3605
|
+
["\u{1F514}", "bell"]
|
|
3606
|
+
] },
|
|
3607
|
+
{ name: "Symbols", items: [
|
|
3608
|
+
["\u{1F525}", "fire"],
|
|
3609
|
+
["\u2728", "sparkles"],
|
|
3610
|
+
["\u{1F389}", "celebrate"],
|
|
3611
|
+
["\u{1F4AF}", "hundred"],
|
|
3612
|
+
["\u26A0\uFE0F", "warning"],
|
|
3613
|
+
["\u274C", "cross"],
|
|
3614
|
+
["\u2B50", "star"],
|
|
3615
|
+
["\u{1F680}", "launch"],
|
|
3616
|
+
["\u{1F49A}", "green heart"],
|
|
3617
|
+
["\u{1F499}", "blue heart"],
|
|
3618
|
+
["\u{1F552}", "clock"],
|
|
3619
|
+
["\u{1F4A1}", "idea"],
|
|
3620
|
+
["\u{1F512}", "lock"],
|
|
3621
|
+
["\u{1F3AF}", "target"]
|
|
3622
|
+
] }
|
|
3623
|
+
];
|
|
3624
|
+
var toGroup = (name, items) => ({
|
|
3625
|
+
name,
|
|
3626
|
+
items: items.map(([ch, kw]) => ({ ch, name: kw }))
|
|
3627
|
+
});
|
|
3628
|
+
function emojiGroups(query) {
|
|
3629
|
+
const q2 = query.trim().toLowerCase();
|
|
3630
|
+
if (!q2) return CATS.map((c) => toGroup(c.name, c.items));
|
|
3631
|
+
const hits = [];
|
|
3632
|
+
for (const c of CATS) for (const e of c.items) {
|
|
3633
|
+
if (e[1].indexOf(q2) >= 0 && !hits.some((h) => h[0] === e[0])) hits.push(e);
|
|
3634
|
+
}
|
|
3635
|
+
if (!hits.length) return [];
|
|
3636
|
+
return [toGroup(`${hits.length} result${hits.length === 1 ? "" : "s"}`, hits)];
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3399
3639
|
// src/element.ts
|
|
3400
|
-
var
|
|
3640
|
+
var DEFAULT_STATUS = "Replies in a few seconds";
|
|
3641
|
+
var ATTACHMENT_ACCEPT = "image/*,application/pdf,text/plain,text/csv";
|
|
3642
|
+
var AWAY_THRESHOLD = 60;
|
|
3643
|
+
var HANDOFF_STATUS = {
|
|
3401
3644
|
ai: null,
|
|
3402
3645
|
awaiting_human: "Connecting you to a person\u2026",
|
|
3403
3646
|
human: "You're chatting with a person"
|
|
3404
3647
|
};
|
|
3405
|
-
var ATTACHMENT_ACCEPT = "image/*,application/pdf,text/plain,text/csv";
|
|
3406
|
-
var AGENT_TYPING_TEXT = "The agent is typing\u2026";
|
|
3407
|
-
var PIN_THRESHOLD = 48;
|
|
3408
|
-
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>';
|
|
3409
|
-
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>';
|
|
3410
|
-
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>';
|
|
3411
|
-
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>';
|
|
3412
3648
|
var OyehelloChatElement = class extends HTMLElement {
|
|
3413
|
-
static observedAttributes = ["api-key", "agent-id", "base-url", "title"];
|
|
3649
|
+
static observedAttributes = ["api-key", "agent-id", "base-url", "title", "theme"];
|
|
3650
|
+
// ── live client state ──
|
|
3414
3651
|
client = null;
|
|
3415
3652
|
off = null;
|
|
3416
3653
|
messages = [];
|
|
@@ -3419,35 +3656,36 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3419
3656
|
handoffState = "ai";
|
|
3420
3657
|
agentTyping = false;
|
|
3421
3658
|
lastError = null;
|
|
3422
|
-
// registerTools() may run before connectedCallback creates the client — buffer until it exists.
|
|
3423
3659
|
pendingTools = {};
|
|
3424
3660
|
pendingFiles = [];
|
|
3425
3661
|
imagePreviewCache = /* @__PURE__ */ new Map();
|
|
3426
3662
|
resolvedUrlCache = /* @__PURE__ */ new Map();
|
|
3427
|
-
// Rendered-markdown cache for FINALIZED assistant/human messages (keyed by message id) so the whole
|
|
3428
|
-
// history isn't re-parsed on every streamed token — only the live streaming message is re-rendered.
|
|
3429
3663
|
mdCache = /* @__PURE__ */ new Map();
|
|
3430
|
-
|
|
3431
|
-
|
|
3664
|
+
receivedAt = /* @__PURE__ */ new Map();
|
|
3665
|
+
// ── widget UI state ──
|
|
3666
|
+
uiOpen = false;
|
|
3432
3667
|
started = false;
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3668
|
+
uiMax = false;
|
|
3669
|
+
emojiOpen = false;
|
|
3670
|
+
emojiQuery = "";
|
|
3671
|
+
away = false;
|
|
3672
|
+
// reader scrolled up
|
|
3436
3673
|
unseen = 0;
|
|
3437
|
-
|
|
3674
|
+
focused = false;
|
|
3675
|
+
teaserActive = false;
|
|
3438
3676
|
renderedCount = 0;
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3677
|
+
renderedBotFinal = 0;
|
|
3678
|
+
timers = [];
|
|
3679
|
+
// ── shadow refs ──
|
|
3680
|
+
oc;
|
|
3442
3681
|
bodyEl;
|
|
3443
3682
|
textareaEl;
|
|
3444
|
-
sendEl;
|
|
3445
3683
|
fileEl;
|
|
3446
|
-
|
|
3447
|
-
badgeEl;
|
|
3684
|
+
titleEl;
|
|
3448
3685
|
statusEl;
|
|
3449
3686
|
errEl;
|
|
3450
|
-
|
|
3687
|
+
emojiListEl;
|
|
3688
|
+
emojiSearchEl;
|
|
3451
3689
|
connectedCallback() {
|
|
3452
3690
|
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
3453
3691
|
const apiKey = this.getAttribute("api-key");
|
|
@@ -3464,18 +3702,42 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3464
3702
|
}
|
|
3465
3703
|
this.off = this.client.on((e) => this.onClientEvent(e));
|
|
3466
3704
|
this.renderShell();
|
|
3467
|
-
this.
|
|
3705
|
+
this.renderMessages();
|
|
3706
|
+
this.updateHeader();
|
|
3468
3707
|
this.updateStatus();
|
|
3708
|
+
this.updateClasses();
|
|
3469
3709
|
let wasOpen = false;
|
|
3470
3710
|
try {
|
|
3471
|
-
wasOpen = sessionStorage.getItem(this.
|
|
3711
|
+
wasOpen = sessionStorage.getItem(this.key("open")) === "1";
|
|
3712
|
+
} catch {
|
|
3713
|
+
}
|
|
3714
|
+
if (wasOpen) {
|
|
3715
|
+
this.openPanel();
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
this.updateClasses(true);
|
|
3719
|
+
this.timers.push(setTimeout(() => {
|
|
3720
|
+
this.oc?.classList.remove("ring");
|
|
3721
|
+
}, 6e3));
|
|
3722
|
+
const teaserText = this.getAttribute("teaser");
|
|
3723
|
+
let dismissed = false;
|
|
3724
|
+
try {
|
|
3725
|
+
dismissed = sessionStorage.getItem(this.key("teaser")) === "0";
|
|
3472
3726
|
} catch {
|
|
3473
3727
|
}
|
|
3474
|
-
if (
|
|
3728
|
+
if (teaserText && !dismissed) {
|
|
3729
|
+
this.timers.push(setTimeout(() => {
|
|
3730
|
+
if (!this.uiOpen) {
|
|
3731
|
+
this.teaserActive = true;
|
|
3732
|
+
this.updateClasses();
|
|
3733
|
+
}
|
|
3734
|
+
}, 1400));
|
|
3735
|
+
}
|
|
3475
3736
|
}
|
|
3476
3737
|
disconnectedCallback() {
|
|
3477
3738
|
this.off?.();
|
|
3478
3739
|
void this.client?.end();
|
|
3740
|
+
for (const t of this.timers) clearTimeout(t);
|
|
3479
3741
|
for (const url of this.imagePreviewCache.values()) {
|
|
3480
3742
|
if (typeof URL.revokeObjectURL === "function") URL.revokeObjectURL(url);
|
|
3481
3743
|
}
|
|
@@ -3485,61 +3747,121 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3485
3747
|
if (this.client) this.client.registerTools(handlers);
|
|
3486
3748
|
else Object.assign(this.pendingTools, handlers);
|
|
3487
3749
|
}
|
|
3488
|
-
|
|
3489
|
-
return `oyehello-chat
|
|
3750
|
+
key(k2) {
|
|
3751
|
+
return `oyehello-chat:${k2}:${this.getAttribute("agent-id") ?? ""}`;
|
|
3752
|
+
}
|
|
3753
|
+
get suggestions() {
|
|
3754
|
+
return (this.getAttribute("suggestions") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
3490
3755
|
}
|
|
3491
3756
|
// ── Shell (built once) ─────────────────────────────────────────────────────────────────────────
|
|
3492
3757
|
renderShell() {
|
|
3493
3758
|
const root = this.shadowRoot;
|
|
3759
|
+
const hasTeaser = !!this.getAttribute("teaser");
|
|
3760
|
+
const hasPrivacy = !!this.getAttribute("privacy-url");
|
|
3761
|
+
const teaserText = this.getAttribute("teaser") ?? "";
|
|
3494
3762
|
root.innerHTML = `
|
|
3495
3763
|
<style>${STYLES}</style>
|
|
3496
|
-
<
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
<
|
|
3507
|
-
<
|
|
3508
|
-
<
|
|
3509
|
-
|
|
3510
|
-
|
|
3764
|
+
<div class="oc${hasTeaser ? " has-teaser" : ""}${hasPrivacy ? " has-privacy" : ""}">
|
|
3765
|
+
<div class="panel" role="dialog" aria-label="Chat">
|
|
3766
|
+
<div class="hd">
|
|
3767
|
+
<span class="hd-grid"></span>
|
|
3768
|
+
<span class="logo">${logoMark("currentColor")}</span>
|
|
3769
|
+
<span class="hd-txt"><span class="hd-title">Chat</span><span class="hd-status"><span class="dot"></span><span class="t"></span></span></span>
|
|
3770
|
+
<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>
|
|
3771
|
+
<button type="button" class="hd-btn min" aria-label="Minimise">${ICON.minus}</button>
|
|
3772
|
+
</div>
|
|
3773
|
+
<div class="err" hidden></div>
|
|
3774
|
+
<div class="body"></div>
|
|
3775
|
+
<span class="emoji-scrim"></span>
|
|
3776
|
+
<div class="emoji-panel">
|
|
3777
|
+
<div class="emoji-search">${ICON.search}<input type="text" placeholder="Search emoji\u2026" aria-label="Search emoji" /></div>
|
|
3778
|
+
<div class="emoji-list"></div>
|
|
3779
|
+
</div>
|
|
3780
|
+
<div class="jump-wrap"><button type="button" class="jump" aria-label="Jump to latest">${ICON.chevronDown}</button></div>
|
|
3781
|
+
<div class="composer">
|
|
3782
|
+
<form class="field">
|
|
3783
|
+
<textarea class="input" rows="1" aria-label="message" placeholder="Ask a question\u2026"></textarea>
|
|
3784
|
+
<div class="acts">
|
|
3785
|
+
<button type="button" class="act attach" aria-label="Attach a file">${ICON.paperclip}</button>
|
|
3786
|
+
<input type="file" accept="${ATTACHMENT_ACCEPT}" multiple hidden />
|
|
3787
|
+
<button type="button" class="act emoji-btn" aria-label="Insert emoji">${ICON.emoji}</button>
|
|
3788
|
+
<button type="button" class="act mic" aria-label="Send a voice note">${ICON.mic}</button>
|
|
3789
|
+
<button type="submit" class="send" aria-label="Send">${ICON.send}</button>
|
|
3790
|
+
</div>
|
|
3791
|
+
</form>
|
|
3792
|
+
<div class="foot">
|
|
3793
|
+
<span>By chatting with us, you agree to our </span><span class="priv">Privacy Policy</span>
|
|
3794
|
+
<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>
|
|
3795
|
+
</div>
|
|
3796
|
+
</div>
|
|
3797
|
+
</div>
|
|
3798
|
+
|
|
3799
|
+
<div class="teaser">
|
|
3800
|
+
<button type="button" class="teaser-btn"><span class="logo">${logoMark("currentColor")}</span><span>${escapeHtml(teaserText)}</span></button>
|
|
3801
|
+
<button type="button" class="teaser-x" aria-label="Dismiss">${ICON.teaserDismiss}</button>
|
|
3802
|
+
</div>
|
|
3803
|
+
|
|
3804
|
+
<button type="button" class="launcher" aria-label="Open chat">
|
|
3805
|
+
<span class="ring"></span>
|
|
3806
|
+
<span class="ic-chat">${logoMark("var(--oc-ac)")}</span>
|
|
3807
|
+
<span class="ic-close">${ICON.close(19)}</span>
|
|
3808
|
+
<span class="badge"></span>
|
|
3809
|
+
</button>
|
|
3511
3810
|
</div>`;
|
|
3512
|
-
this.
|
|
3811
|
+
this.oc = root.querySelector(".oc");
|
|
3513
3812
|
this.bodyEl = root.querySelector(".body");
|
|
3514
|
-
this.textareaEl = root.querySelector("textarea");
|
|
3515
|
-
this.sendEl = root.querySelector(".send");
|
|
3813
|
+
this.textareaEl = root.querySelector("textarea.input");
|
|
3516
3814
|
this.fileEl = root.querySelector('input[type="file"]');
|
|
3517
|
-
this.
|
|
3518
|
-
this.
|
|
3519
|
-
this.statusEl = root.querySelector(".status");
|
|
3815
|
+
this.titleEl = root.querySelector(".hd-title");
|
|
3816
|
+
this.statusEl = root.querySelector(".hd-status .t");
|
|
3520
3817
|
this.errEl = root.querySelector(".err");
|
|
3521
|
-
this.
|
|
3522
|
-
root.querySelector(".
|
|
3523
|
-
root.querySelector(".
|
|
3818
|
+
this.emojiListEl = root.querySelector(".emoji-list");
|
|
3819
|
+
this.emojiSearchEl = root.querySelector(".emoji-search input");
|
|
3820
|
+
root.querySelector(".launcher").addEventListener("click", () => this.toggle());
|
|
3821
|
+
root.querySelector(".hd-btn.min").addEventListener("click", () => this.closePanel());
|
|
3822
|
+
root.querySelector(".hd-btn.max").addEventListener("click", () => {
|
|
3823
|
+
this.uiMax = !this.uiMax;
|
|
3824
|
+
this.updateClasses();
|
|
3825
|
+
});
|
|
3826
|
+
root.querySelector(".teaser-btn").addEventListener("click", () => this.openPanel());
|
|
3827
|
+
root.querySelector(".teaser-x").addEventListener("click", () => this.dismissTeaser());
|
|
3828
|
+
root.querySelector(".jump").addEventListener("click", () => this.scrollToBottom());
|
|
3524
3829
|
root.querySelector(".attach").addEventListener("click", () => this.fileEl.click());
|
|
3525
3830
|
this.fileEl.addEventListener("change", () => {
|
|
3526
3831
|
this.pendingFiles = this.fileEl.files ? Array.from(this.fileEl.files) : [];
|
|
3527
3832
|
});
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3833
|
+
root.querySelector(".emoji-btn").addEventListener("click", () => this.toggleEmoji());
|
|
3834
|
+
root.querySelector(".emoji-scrim").addEventListener("click", () => this.closeEmoji());
|
|
3835
|
+
this.emojiSearchEl.addEventListener("input", () => {
|
|
3836
|
+
this.emojiQuery = this.emojiSearchEl.value;
|
|
3837
|
+
this.renderEmoji();
|
|
3838
|
+
});
|
|
3839
|
+
this.bodyEl.addEventListener("scroll", () => this.onScroll());
|
|
3840
|
+
root.querySelector("form.field").addEventListener("submit", (e) => {
|
|
3531
3841
|
e.preventDefault();
|
|
3532
3842
|
this.doSend();
|
|
3533
3843
|
});
|
|
3534
|
-
this.textareaEl.addEventListener("input", () =>
|
|
3844
|
+
this.textareaEl.addEventListener("input", () => {
|
|
3845
|
+
this.autoGrow();
|
|
3846
|
+
this.updateClasses();
|
|
3847
|
+
});
|
|
3848
|
+
this.textareaEl.addEventListener("focus", () => {
|
|
3849
|
+
this.focused = true;
|
|
3850
|
+
this.updateClasses();
|
|
3851
|
+
});
|
|
3852
|
+
this.textareaEl.addEventListener("blur", () => {
|
|
3853
|
+
this.focused = false;
|
|
3854
|
+
this.updateClasses();
|
|
3855
|
+
});
|
|
3535
3856
|
this.textareaEl.addEventListener("keydown", (e) => {
|
|
3536
3857
|
if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
|
|
3537
3858
|
e.preventDefault();
|
|
3538
3859
|
this.doSend();
|
|
3539
3860
|
}
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3861
|
+
if (e.key === "Escape") {
|
|
3862
|
+
if (this.emojiOpen) this.closeEmoji();
|
|
3863
|
+
else this.closePanel();
|
|
3864
|
+
}
|
|
3543
3865
|
});
|
|
3544
3866
|
}
|
|
3545
3867
|
renderError(msg) {
|
|
@@ -3547,37 +3869,62 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3547
3869
|
console.error(msg);
|
|
3548
3870
|
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>`;
|
|
3549
3871
|
}
|
|
3872
|
+
// ── State-class sync ──────────────────────────────────────────────────────────────────────────
|
|
3873
|
+
updateClasses(initialRing = false) {
|
|
3874
|
+
if (!this.oc) return;
|
|
3875
|
+
const c = this.oc.classList;
|
|
3876
|
+
c.toggle("open", this.uiOpen);
|
|
3877
|
+
c.toggle("max", this.uiMax);
|
|
3878
|
+
c.toggle("emoji", this.emojiOpen);
|
|
3879
|
+
c.toggle("teaser", this.teaserActive);
|
|
3880
|
+
c.toggle("away", this.away);
|
|
3881
|
+
c.toggle("focus", this.focused);
|
|
3882
|
+
c.toggle("ready", (this.textareaEl?.value.trim().length ?? 0) > 0);
|
|
3883
|
+
c.toggle("unread", this.unseen > 0);
|
|
3884
|
+
if (initialRing) c.add("ring");
|
|
3885
|
+
const badge = this.oc.querySelector(".badge");
|
|
3886
|
+
if (badge) badge.textContent = this.unseen > 9 ? "9+" : String(this.unseen);
|
|
3887
|
+
const launcher = this.oc.querySelector(".launcher");
|
|
3888
|
+
if (launcher) launcher.setAttribute("aria-label", this.uiOpen ? "Close chat" : "Open chat");
|
|
3889
|
+
}
|
|
3550
3890
|
// ── Open / close ───────────────────────────────────────────────────────────────────────────────
|
|
3551
|
-
|
|
3552
|
-
this.
|
|
3891
|
+
toggle() {
|
|
3892
|
+
this.uiOpen ? this.closePanel() : this.openPanel();
|
|
3553
3893
|
}
|
|
3554
3894
|
openPanel() {
|
|
3555
|
-
this.
|
|
3895
|
+
this.uiOpen = true;
|
|
3896
|
+
this.teaserActive = false;
|
|
3897
|
+
this.unseen = 0;
|
|
3556
3898
|
try {
|
|
3557
|
-
sessionStorage.setItem(this.
|
|
3899
|
+
sessionStorage.setItem(this.key("open"), "1");
|
|
3558
3900
|
} catch {
|
|
3559
3901
|
}
|
|
3560
|
-
this.panelEl.hidden = false;
|
|
3561
|
-
this.panelEl.classList.add("enter");
|
|
3562
|
-
requestAnimationFrame(() => this.panelEl.classList.remove("enter"));
|
|
3563
3902
|
if (!this.started && this.client) {
|
|
3564
3903
|
this.started = true;
|
|
3565
3904
|
void this.client.start();
|
|
3566
3905
|
}
|
|
3906
|
+
this.updateClasses();
|
|
3567
3907
|
this.renderMessages();
|
|
3568
|
-
this.pinned = true;
|
|
3569
|
-
this.unseen = 0;
|
|
3570
|
-
this.updateBadge();
|
|
3571
3908
|
this.scrollToBottom();
|
|
3572
|
-
if (!this.textareaEl.disabled) this.textareaEl.focus();
|
|
3909
|
+
if (!this.textareaEl.disabled) setTimeout(() => this.textareaEl.focus(), 60);
|
|
3573
3910
|
}
|
|
3574
3911
|
closePanel() {
|
|
3575
|
-
this.
|
|
3912
|
+
this.uiOpen = false;
|
|
3913
|
+
this.uiMax = false;
|
|
3914
|
+
this.emojiOpen = false;
|
|
3915
|
+
try {
|
|
3916
|
+
sessionStorage.setItem(this.key("open"), "0");
|
|
3917
|
+
} catch {
|
|
3918
|
+
}
|
|
3919
|
+
this.updateClasses();
|
|
3920
|
+
}
|
|
3921
|
+
dismissTeaser() {
|
|
3922
|
+
this.teaserActive = false;
|
|
3576
3923
|
try {
|
|
3577
|
-
sessionStorage.setItem(this.
|
|
3924
|
+
sessionStorage.setItem(this.key("teaser"), "0");
|
|
3578
3925
|
} catch {
|
|
3579
3926
|
}
|
|
3580
|
-
this.
|
|
3927
|
+
this.updateClasses();
|
|
3581
3928
|
}
|
|
3582
3929
|
// ── Client events ─────────────────────────────────────────────────────────────────────────────
|
|
3583
3930
|
onClientEvent(e) {
|
|
@@ -3587,24 +3934,23 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3587
3934
|
if (e.state === "ready" || e.state === "opening") this.lastError = null;
|
|
3588
3935
|
} else if (e.type === "agent") {
|
|
3589
3936
|
this.agentName = e.agent.name;
|
|
3590
|
-
this.
|
|
3937
|
+
this.updateHeader();
|
|
3591
3938
|
} else if (e.type === "handoff") this.handoffState = e.state;
|
|
3592
3939
|
else if (e.type === "agentTyping") this.agentTyping = e.active;
|
|
3593
3940
|
else if (e.type === "error") this.lastError = e.error.message;
|
|
3594
3941
|
this.updateStatus();
|
|
3595
3942
|
this.renderMessages();
|
|
3596
3943
|
}
|
|
3597
|
-
|
|
3944
|
+
updateHeader() {
|
|
3598
3945
|
if (this.titleEl) this.titleEl.textContent = this.getAttribute("title") ?? this.agentName ?? "Chat";
|
|
3599
3946
|
}
|
|
3600
3947
|
updateStatus() {
|
|
3601
3948
|
if (!this.textareaEl) return;
|
|
3602
3949
|
const disabled = this.state !== "ready";
|
|
3603
3950
|
this.textareaEl.disabled = disabled;
|
|
3604
|
-
|
|
3605
|
-
const
|
|
3606
|
-
this.statusEl.
|
|
3607
|
-
this.statusEl.textContent = st2 ?? "";
|
|
3951
|
+
const handoff = HANDOFF_STATUS[this.handoffState];
|
|
3952
|
+
const text2 = this.state === "opening" ? "Connecting\u2026" : handoff ?? (this.getAttribute("status") ?? DEFAULT_STATUS);
|
|
3953
|
+
if (this.statusEl) this.statusEl.textContent = text2;
|
|
3608
3954
|
if (this.state === "error" && this.lastError) {
|
|
3609
3955
|
this.errEl.hidden = false;
|
|
3610
3956
|
this.errEl.innerHTML = `<span>Couldn't start chat: ${escapeHtml(this.lastError)}</span><button class="retry" type="button">Retry</button>`;
|
|
@@ -3620,58 +3966,73 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3620
3966
|
renderMessages() {
|
|
3621
3967
|
if (!this.bodyEl) return;
|
|
3622
3968
|
const body = this.bodyEl;
|
|
3623
|
-
const atBottom = this.open && this.pinned;
|
|
3624
3969
|
const prevTop = body.scrollTop;
|
|
3625
3970
|
const added = this.messages.length - this.renderedCount;
|
|
3626
|
-
const
|
|
3627
|
-
|
|
3628
|
-
|
|
3971
|
+
const botFinal = this.messages.filter((m2) => m2.role === "assistant" && !m2.streaming).length;
|
|
3972
|
+
const newBotFinal = botFinal > this.renderedBotFinal;
|
|
3973
|
+
const now = Date.now();
|
|
3974
|
+
for (const m2 of this.messages) if (!this.receivedAt.has(m2.id)) this.receivedAt.set(m2.id, now);
|
|
3975
|
+
const lastBot = (() => {
|
|
3976
|
+
for (let i = this.messages.length - 1; i >= 0; i--) if (this.messages[i]?.role === "assistant") return i;
|
|
3977
|
+
return -1;
|
|
3978
|
+
})();
|
|
3979
|
+
let html2 = this.messages.map((m2, i) => this.turnHtml(m2, i === lastBot && !this.agentTyping)).join("");
|
|
3980
|
+
if (this.agentTyping) html2 += '<span class="typing"><span></span><span></span><span></span></span>';
|
|
3981
|
+
const sugg = this.suggestions;
|
|
3982
|
+
if (sugg.length > 0 && !this.agentTyping && this.state === "ready") {
|
|
3983
|
+
html2 += `<span class="chips">${sugg.map((s, i) => `<button type="button" class="chip" data-chip="${i}">${escapeHtml(s)}</button>`).join("")}</span>`;
|
|
3984
|
+
}
|
|
3629
3985
|
body.innerHTML = html2;
|
|
3630
3986
|
this.hydrateAttachmentImages();
|
|
3987
|
+
this.bindChips();
|
|
3631
3988
|
this.renderedCount = this.messages.length;
|
|
3632
|
-
|
|
3989
|
+
this.renderedBotFinal = botFinal;
|
|
3990
|
+
if (!this.uiOpen) {
|
|
3991
|
+
if (added > 0) this.unseen += added;
|
|
3992
|
+
this.updateClasses();
|
|
3993
|
+
return;
|
|
3994
|
+
}
|
|
3995
|
+
if (newBotFinal && !this.away) this.parkLastQuestion();
|
|
3996
|
+
else if (!this.away) this.scrollToBottom();
|
|
3633
3997
|
else {
|
|
3634
|
-
|
|
3998
|
+
body.scrollTop = prevTop;
|
|
3635
3999
|
if (added > 0) this.unseen += added;
|
|
3636
4000
|
}
|
|
3637
|
-
this.
|
|
3638
|
-
this.updateBadge();
|
|
4001
|
+
this.updateClasses();
|
|
3639
4002
|
}
|
|
3640
|
-
|
|
4003
|
+
// After a reply lands, float the question that prompted it to the top so the answer reads from its
|
|
4004
|
+
// first line rather than its last (the design's behaviour).
|
|
4005
|
+
parkLastQuestion() {
|
|
4006
|
+
const users = this.bodyEl.querySelectorAll(".turn.user");
|
|
4007
|
+
const last = users[users.length - 1];
|
|
4008
|
+
if (!last) {
|
|
4009
|
+
this.scrollToBottom();
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
const top = Math.max(0, Math.min(last.offsetTop - 22, this.bodyEl.scrollHeight - this.bodyEl.clientHeight));
|
|
4013
|
+
this.bodyEl.scrollTo({ top, behavior: "smooth" });
|
|
4014
|
+
}
|
|
4015
|
+
onScroll() {
|
|
3641
4016
|
const b2 = this.bodyEl;
|
|
3642
|
-
const
|
|
3643
|
-
this.
|
|
3644
|
-
|
|
3645
|
-
this.unseen = 0;
|
|
3646
|
-
this.
|
|
4017
|
+
const away = b2.scrollHeight - b2.scrollTop - b2.clientHeight > AWAY_THRESHOLD;
|
|
4018
|
+
if (away !== this.away) {
|
|
4019
|
+
this.away = away;
|
|
4020
|
+
if (!away) this.unseen = 0;
|
|
4021
|
+
this.updateClasses();
|
|
3647
4022
|
}
|
|
3648
|
-
this.updatePill();
|
|
3649
4023
|
}
|
|
3650
4024
|
scrollToBottom() {
|
|
3651
4025
|
if (!this.bodyEl) return;
|
|
3652
4026
|
this.bodyEl.scrollTop = this.bodyEl.scrollHeight;
|
|
3653
|
-
this.
|
|
4027
|
+
this.away = false;
|
|
3654
4028
|
this.unseen = 0;
|
|
3655
|
-
this.
|
|
3656
|
-
this.updateBadge();
|
|
3657
|
-
}
|
|
3658
|
-
updatePill() {
|
|
3659
|
-
if (!this.pillEl) return;
|
|
3660
|
-
this.pillEl.hidden = !(this.open && !this.pinned && this.unseen > 0);
|
|
3661
|
-
const t = this.pillEl.querySelector(".pill-text");
|
|
3662
|
-
if (t) t.textContent = this.unseen === 1 ? "1 new message" : `${this.unseen} new messages`;
|
|
3663
|
-
}
|
|
3664
|
-
updateBadge() {
|
|
3665
|
-
if (!this.badgeEl) return;
|
|
3666
|
-
const show = !this.open && this.unseen > 0;
|
|
3667
|
-
this.badgeEl.hidden = !show;
|
|
3668
|
-
this.badgeEl.textContent = this.unseen > 9 ? "9+" : String(this.unseen);
|
|
4029
|
+
this.updateClasses();
|
|
3669
4030
|
}
|
|
3670
4031
|
// ── Composer ──────────────────────────────────────────────────────────────────────────────────
|
|
3671
4032
|
autoGrow() {
|
|
3672
4033
|
const ta = this.textareaEl;
|
|
3673
4034
|
ta.style.height = "auto";
|
|
3674
|
-
ta.style.height = `${Math.min(ta.scrollHeight,
|
|
4035
|
+
ta.style.height = `${Math.min(ta.scrollHeight, 108)}px`;
|
|
3675
4036
|
}
|
|
3676
4037
|
doSend() {
|
|
3677
4038
|
const text2 = this.textareaEl.value;
|
|
@@ -3684,15 +4045,63 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3684
4045
|
this.imagePreviewCache.set(f2.name, URL.createObjectURL(f2));
|
|
3685
4046
|
}
|
|
3686
4047
|
}
|
|
3687
|
-
this.
|
|
4048
|
+
this.away = false;
|
|
4049
|
+
this.emojiOpen = false;
|
|
3688
4050
|
void this.client?.sendMessage(text2, files.length > 0 ? files : void 0);
|
|
3689
4051
|
this.textareaEl.value = "";
|
|
3690
4052
|
this.textareaEl.style.height = "auto";
|
|
4053
|
+
this.updateClasses();
|
|
4054
|
+
}
|
|
4055
|
+
sendChip(text2) {
|
|
4056
|
+
this.away = false;
|
|
4057
|
+
this.emojiOpen = false;
|
|
4058
|
+
void this.client?.sendMessage(text2);
|
|
4059
|
+
this.updateClasses();
|
|
4060
|
+
}
|
|
4061
|
+
bindChips() {
|
|
4062
|
+
const sugg = this.suggestions;
|
|
4063
|
+
this.bodyEl.querySelectorAll(".chip[data-chip]").forEach((btn) => {
|
|
4064
|
+
const i = Number(btn.getAttribute("data-chip"));
|
|
4065
|
+
const text2 = sugg[i];
|
|
4066
|
+
if (text2) btn.addEventListener("click", () => this.sendChip(text2));
|
|
4067
|
+
});
|
|
4068
|
+
}
|
|
4069
|
+
// ── Emoji ─────────────────────────────────────────────────────────────────────────────────────
|
|
4070
|
+
toggleEmoji() {
|
|
4071
|
+
this.emojiOpen = !this.emojiOpen;
|
|
4072
|
+
this.emojiQuery = "";
|
|
4073
|
+
this.emojiSearchEl.value = "";
|
|
4074
|
+
this.renderEmoji();
|
|
4075
|
+
this.updateClasses();
|
|
4076
|
+
if (this.emojiOpen) setTimeout(() => this.emojiSearchEl.focus(), 60);
|
|
4077
|
+
}
|
|
4078
|
+
closeEmoji() {
|
|
4079
|
+
this.emojiOpen = false;
|
|
4080
|
+
this.emojiQuery = "";
|
|
4081
|
+
this.updateClasses();
|
|
4082
|
+
}
|
|
4083
|
+
renderEmoji() {
|
|
4084
|
+
const groups = emojiGroups(this.emojiQuery);
|
|
4085
|
+
let html2 = groups.map((g) => `
|
|
4086
|
+
<div class="emoji-grp"><span class="emoji-grp-name">${escapeHtml(g.name)}</span>
|
|
4087
|
+
<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>
|
|
4088
|
+
</div>`).join("");
|
|
4089
|
+
if (groups.length === 0) html2 = '<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>';
|
|
4090
|
+
this.emojiListEl.innerHTML = html2;
|
|
4091
|
+
this.emojiListEl.querySelectorAll("button[data-ch]").forEach((btn) => {
|
|
4092
|
+
btn.addEventListener("click", () => {
|
|
4093
|
+
this.textareaEl.value += btn.getAttribute("data-ch") ?? "";
|
|
4094
|
+
this.emojiOpen = false;
|
|
4095
|
+
this.autoGrow();
|
|
4096
|
+
this.updateClasses();
|
|
4097
|
+
this.textareaEl.focus();
|
|
4098
|
+
});
|
|
4099
|
+
});
|
|
3691
4100
|
}
|
|
3692
4101
|
// ── Bubble rendering ──────────────────────────────────────────────────────────────────────────
|
|
3693
|
-
|
|
4102
|
+
turnHtml(m2, isLastBot) {
|
|
4103
|
+
const role = m2.role === "user" ? "user" : m2.role === "human" ? "human" : "bot";
|
|
3694
4104
|
const atts = (m2.attachments ?? []).map((a) => this.renderAttachment(a)).join("");
|
|
3695
|
-
const label = m2.role === "human" ? '<div class="human-label">Support</div>' : "";
|
|
3696
4105
|
let content;
|
|
3697
4106
|
if (m2.role === "assistant") {
|
|
3698
4107
|
let rendered = m2.streaming ? void 0 : this.mdCache.get(m2.id);
|
|
@@ -3700,30 +4109,32 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3700
4109
|
rendered = renderMarkdown(m2.text || "");
|
|
3701
4110
|
if (!m2.streaming) this.mdCache.set(m2.id, rendered);
|
|
3702
4111
|
}
|
|
3703
|
-
content = `<
|
|
4112
|
+
content = `<span class="b">${atts}<span class="md">${rendered}</span></span>`;
|
|
3704
4113
|
} else {
|
|
3705
|
-
|
|
4114
|
+
const label = m2.role === "human" ? '<div class="human-label">Support</div>' : "";
|
|
4115
|
+
content = `<span class="b">${label}${atts}${escapeHtml(m2.text)}</span>`;
|
|
3706
4116
|
}
|
|
3707
|
-
const
|
|
3708
|
-
const
|
|
3709
|
-
|
|
3710
|
-
|
|
4117
|
+
const src = m2.sources && m2.sources.length > 0 ? `<span class="src">${ICON.book}<span class="t">${escapeHtml("From " + (m2.sources[0]?.title ?? ""))}</span></span>` : "";
|
|
4118
|
+
const byline = role === "bot" && isLastBot ? `<span class="byline">${escapeHtml((this.agentName ?? "OyeHello") + " \xB7 AI agent \xB7 " + this.relTime(m2.id))}</span>` : "";
|
|
4119
|
+
return `<span class="turn ${role}">${content}${src}${byline}</span>`;
|
|
4120
|
+
}
|
|
4121
|
+
relTime(id) {
|
|
4122
|
+
const t = this.receivedAt.get(id);
|
|
4123
|
+
if (!t) return "just now";
|
|
4124
|
+
const mins = Math.floor((Date.now() - t) / 6e4);
|
|
4125
|
+
return mins <= 0 ? "just now" : `${mins} min ago`;
|
|
3711
4126
|
}
|
|
3712
4127
|
renderAttachment(a) {
|
|
3713
4128
|
if (a.contentType.startsWith("image/")) {
|
|
3714
4129
|
const cached = (a.name ? this.imagePreviewCache.get(a.name) : void 0) ?? this.resolvedUrlCache.get(a.storageRef);
|
|
3715
4130
|
const srcAttr = cached ? ` src="${cached}"` : "";
|
|
3716
|
-
return `<img class="att-thumb" data-oc-ref="${
|
|
4131
|
+
return `<img class="att-thumb" data-oc-ref="${this.escapeAttr(a.storageRef)}"${srcAttr} alt="${this.escapeAttr(a.name ?? "image")}"/>`;
|
|
3717
4132
|
}
|
|
3718
4133
|
return `<div class="att-chip">\u{1F4CE} ${escapeHtml(a.name ?? a.contentType)}</div>`;
|
|
3719
4134
|
}
|
|
3720
|
-
renderSource(s) {
|
|
3721
|
-
return `<div class="src-chip" title="${escapeHtml(s.snippet)}">${escapeHtml(s.title)}</div>`;
|
|
3722
|
-
}
|
|
3723
4135
|
hydrateAttachmentImages() {
|
|
3724
4136
|
if (!this.client) return;
|
|
3725
|
-
|
|
3726
|
-
imgs.forEach((img) => {
|
|
4137
|
+
this.shadowRoot.querySelectorAll("img.att-thumb[data-oc-ref]").forEach((img) => {
|
|
3727
4138
|
if (img.hasAttribute("src")) return;
|
|
3728
4139
|
const ref = img.getAttribute("data-oc-ref");
|
|
3729
4140
|
if (!ref) return;
|
|
@@ -3734,6 +4145,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3734
4145
|
});
|
|
3735
4146
|
});
|
|
3736
4147
|
}
|
|
4148
|
+
escapeAttr(s) {
|
|
4149
|
+
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
4150
|
+
}
|
|
3737
4151
|
};
|
|
3738
4152
|
function escapeHtml(s) {
|
|
3739
4153
|
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|