@customerhero/react 0.0.1 → 1.0.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/dist/index.cjs +1107 -118
- package/dist/index.d.cts +36 -3
- package/dist/index.d.ts +36 -3
- package/dist/index.js +1104 -110
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/chat-widget.tsx
|
|
2
|
-
import { useEffect as
|
|
2
|
+
import { useEffect as useEffect8, useRef as useRef4 } from "react";
|
|
3
3
|
|
|
4
4
|
// src/context.tsx
|
|
5
5
|
import {
|
|
@@ -52,13 +52,26 @@ function useChat() {
|
|
|
52
52
|
...state,
|
|
53
53
|
t: client.t,
|
|
54
54
|
sendMessage: useCallback(
|
|
55
|
-
(message) => client.sendMessage(message),
|
|
55
|
+
(message, options) => client.sendMessage(message, options),
|
|
56
|
+
[client]
|
|
57
|
+
),
|
|
58
|
+
uploadAttachment: useCallback(
|
|
59
|
+
(blob, options) => client.uploadAttachment(blob, options),
|
|
56
60
|
[client]
|
|
57
61
|
),
|
|
58
62
|
rateMessage: useCallback(
|
|
59
63
|
(messageId, rating) => client.rateMessage(messageId, rating),
|
|
60
64
|
[client]
|
|
61
65
|
),
|
|
66
|
+
approveAction: useCallback(
|
|
67
|
+
(pendingId) => client.approveAction(pendingId),
|
|
68
|
+
[client]
|
|
69
|
+
),
|
|
70
|
+
cancelAction: useCallback(
|
|
71
|
+
(pendingId) => client.cancelAction(pendingId),
|
|
72
|
+
[client]
|
|
73
|
+
),
|
|
74
|
+
setLocale: useCallback((tag) => client.setLocale(tag), [client]),
|
|
62
75
|
toggle: useCallback(() => client.toggle(), [client]),
|
|
63
76
|
open: useCallback(() => client.open(), [client]),
|
|
64
77
|
close: useCallback(() => client.close(), [client]),
|
|
@@ -89,7 +102,7 @@ function useReducedMotion() {
|
|
|
89
102
|
// src/components/chat-bubble.tsx
|
|
90
103
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
91
104
|
function ChatBubble() {
|
|
92
|
-
const { toggle, config, t } = useChat();
|
|
105
|
+
const { toggle, config, t, isRtl } = useChat();
|
|
93
106
|
const reduced = useReducedMotion();
|
|
94
107
|
const [mounted, setMounted] = useState2(false);
|
|
95
108
|
useEffect3(() => {
|
|
@@ -97,10 +110,11 @@ function ChatBubble() {
|
|
|
97
110
|
return () => cancelAnimationFrame(id);
|
|
98
111
|
}, []);
|
|
99
112
|
const visible = mounted;
|
|
113
|
+
const effectivePosition = isRtl ? config.position === "bottom-right" ? "bottom-left" : "bottom-right" : config.position;
|
|
100
114
|
const style = {
|
|
101
115
|
position: "fixed",
|
|
102
116
|
bottom: 20,
|
|
103
|
-
[
|
|
117
|
+
[effectivePosition === "bottom-left" ? "left" : "right"]: 20,
|
|
104
118
|
width: 56,
|
|
105
119
|
height: 56,
|
|
106
120
|
borderRadius: "50%",
|
|
@@ -124,6 +138,7 @@ function ChatBubble() {
|
|
|
124
138
|
{
|
|
125
139
|
onClick: toggle,
|
|
126
140
|
style,
|
|
141
|
+
dir: isRtl ? "rtl" : "ltr",
|
|
127
142
|
"aria-label": t("open_chat"),
|
|
128
143
|
onMouseEnter: (e) => {
|
|
129
144
|
if (!reduced) e.currentTarget.style.transform = "scale(1.1)";
|
|
@@ -150,7 +165,7 @@ function ChatBubble() {
|
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
// src/components/chat-window.tsx
|
|
153
|
-
import { useEffect as
|
|
168
|
+
import { useEffect as useEffect7, useState as useState7 } from "react";
|
|
154
169
|
|
|
155
170
|
// src/components/chat-header.tsx
|
|
156
171
|
import { useState as useState3, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
@@ -369,8 +384,602 @@ function ChatHeader() {
|
|
|
369
384
|
}
|
|
370
385
|
|
|
371
386
|
// src/components/chat-messages.tsx
|
|
372
|
-
import { useEffect as useEffect5, useRef as useRef3, useState as
|
|
387
|
+
import { useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
|
|
388
|
+
|
|
389
|
+
// src/markdown/render.tsx
|
|
373
390
|
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
391
|
+
function parseBlocks(source) {
|
|
392
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
393
|
+
const blocks = [];
|
|
394
|
+
let i = 0;
|
|
395
|
+
while (i < lines.length) {
|
|
396
|
+
const line = lines[i];
|
|
397
|
+
if (line.trim() === "") {
|
|
398
|
+
i++;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const fenceMatch = line.match(/^```(.*)$/);
|
|
402
|
+
if (fenceMatch) {
|
|
403
|
+
const lang = fenceMatch[1].trim() || void 0;
|
|
404
|
+
const body2 = [];
|
|
405
|
+
i++;
|
|
406
|
+
while (i < lines.length && !/^```\s*$/.test(lines[i])) {
|
|
407
|
+
body2.push(lines[i]);
|
|
408
|
+
i++;
|
|
409
|
+
}
|
|
410
|
+
if (i < lines.length) i++;
|
|
411
|
+
blocks.push({ kind: "code", lines: body2, lang });
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.*?)\s*#*\s*$/);
|
|
415
|
+
if (headingMatch) {
|
|
416
|
+
blocks.push({
|
|
417
|
+
kind: "heading",
|
|
418
|
+
level: headingMatch[1].length,
|
|
419
|
+
lines: [headingMatch[2]]
|
|
420
|
+
});
|
|
421
|
+
i++;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (/^(\s*)[-*+]\s+/.test(line)) {
|
|
425
|
+
const body2 = [];
|
|
426
|
+
while (i < lines.length && /^(\s*)[-*+]\s+/.test(lines[i])) {
|
|
427
|
+
body2.push(lines[i].replace(/^(\s*)[-*+]\s+/, ""));
|
|
428
|
+
i++;
|
|
429
|
+
}
|
|
430
|
+
blocks.push({ kind: "ul", lines: body2 });
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (/^(\s*)\d+\.\s+/.test(line)) {
|
|
434
|
+
const body2 = [];
|
|
435
|
+
while (i < lines.length && /^(\s*)\d+\.\s+/.test(lines[i])) {
|
|
436
|
+
body2.push(lines[i].replace(/^(\s*)\d+\.\s+/, ""));
|
|
437
|
+
i++;
|
|
438
|
+
}
|
|
439
|
+
blocks.push({ kind: "ol", lines: body2 });
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (/^>\s?/.test(line)) {
|
|
443
|
+
const body2 = [];
|
|
444
|
+
while (i < lines.length && /^>\s?/.test(lines[i])) {
|
|
445
|
+
body2.push(lines[i].replace(/^>\s?/, ""));
|
|
446
|
+
i++;
|
|
447
|
+
}
|
|
448
|
+
blocks.push({ kind: "blockquote", lines: body2 });
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
const body = [line];
|
|
452
|
+
i++;
|
|
453
|
+
while (i < lines.length) {
|
|
454
|
+
const next = lines[i];
|
|
455
|
+
if (next.trim() === "") break;
|
|
456
|
+
if (/^```/.test(next) || /^#{1,6}\s/.test(next) || /^(\s*)[-*+]\s+/.test(next) || /^(\s*)\d+\.\s+/.test(next) || /^>\s?/.test(next)) {
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
body.push(next);
|
|
460
|
+
i++;
|
|
461
|
+
}
|
|
462
|
+
blocks.push({ kind: "paragraph", lines: body });
|
|
463
|
+
}
|
|
464
|
+
return blocks;
|
|
465
|
+
}
|
|
466
|
+
function findClosing(text, from, end) {
|
|
467
|
+
let i = from;
|
|
468
|
+
while (i <= text.length - end.length) {
|
|
469
|
+
const c = text[i];
|
|
470
|
+
if (c === "\\") {
|
|
471
|
+
i += 2;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (text.startsWith(end, i)) return i;
|
|
475
|
+
i++;
|
|
476
|
+
}
|
|
477
|
+
return -1;
|
|
478
|
+
}
|
|
479
|
+
function renderInlineText(text, ctx) {
|
|
480
|
+
const out = [];
|
|
481
|
+
let buffer = "";
|
|
482
|
+
let i = 0;
|
|
483
|
+
const flushBuffer = () => {
|
|
484
|
+
if (buffer.length > 0) {
|
|
485
|
+
out.push(buffer);
|
|
486
|
+
buffer = "";
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
while (i < text.length) {
|
|
490
|
+
const c = text[i];
|
|
491
|
+
if (c === "\\" && i + 1 < text.length) {
|
|
492
|
+
buffer += text[i + 1];
|
|
493
|
+
i += 2;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (c === " " && text[i + 1] === " " && text[i + 2] === "\n") {
|
|
497
|
+
flushBuffer();
|
|
498
|
+
out.push(/* @__PURE__ */ jsx4("br", {}, ctx.nextKey()));
|
|
499
|
+
i += 3;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (c === "\n") {
|
|
503
|
+
buffer += " ";
|
|
504
|
+
i++;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (c === "`") {
|
|
508
|
+
const close = findClosing(text, i + 1, "`");
|
|
509
|
+
if (close !== -1) {
|
|
510
|
+
flushBuffer();
|
|
511
|
+
out.push(
|
|
512
|
+
/* @__PURE__ */ jsx4(
|
|
513
|
+
"code",
|
|
514
|
+
{
|
|
515
|
+
style: {
|
|
516
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
517
|
+
fontSize: "0.92em",
|
|
518
|
+
background: "rgba(0,0,0,0.06)",
|
|
519
|
+
padding: "1px 4px",
|
|
520
|
+
borderRadius: 3
|
|
521
|
+
},
|
|
522
|
+
children: text.slice(i + 1, close)
|
|
523
|
+
},
|
|
524
|
+
ctx.nextKey()
|
|
525
|
+
)
|
|
526
|
+
);
|
|
527
|
+
i = close + 1;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if ((text.startsWith("**", i) || text.startsWith("__", i)) && i + 2 < text.length) {
|
|
532
|
+
const marker = text.slice(i, i + 2);
|
|
533
|
+
const close = findClosing(text, i + 2, marker);
|
|
534
|
+
if (close !== -1 && close > i + 2) {
|
|
535
|
+
flushBuffer();
|
|
536
|
+
const inner = renderInlineText(text.slice(i + 2, close), ctx);
|
|
537
|
+
out.push(/* @__PURE__ */ jsx4("strong", { children: inner }, ctx.nextKey()));
|
|
538
|
+
i = close + 2;
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (c === "*" || c === "_") {
|
|
543
|
+
const prev = i === 0 ? " " : text[i - 1];
|
|
544
|
+
const isWordBoundaryBefore = c === "*" || !/\w/.test(prev);
|
|
545
|
+
if (isWordBoundaryBefore) {
|
|
546
|
+
const close = findClosing(text, i + 1, c);
|
|
547
|
+
const prevOfClose = close === -1 ? " " : close + 1 < text.length ? text[close + 1] : " ";
|
|
548
|
+
const isWordBoundaryAfter = c === "*" || !/\w/.test(prevOfClose);
|
|
549
|
+
if (close !== -1 && close > i + 1 && text[close + 1] !== c && isWordBoundaryAfter) {
|
|
550
|
+
flushBuffer();
|
|
551
|
+
const inner = renderInlineText(text.slice(i + 1, close), ctx);
|
|
552
|
+
out.push(/* @__PURE__ */ jsx4("em", { children: inner }, ctx.nextKey()));
|
|
553
|
+
i = close + 1;
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (c === "[") {
|
|
559
|
+
const closeLabel = findClosing(text, i + 1, "]");
|
|
560
|
+
if (closeLabel !== -1 && text[closeLabel + 1] === "(") {
|
|
561
|
+
const closeUrl = findClosing(text, closeLabel + 2, ")");
|
|
562
|
+
if (closeUrl !== -1) {
|
|
563
|
+
const label = text.slice(i + 1, closeLabel);
|
|
564
|
+
const url = text.slice(closeLabel + 2, closeUrl).trim();
|
|
565
|
+
if (isSafeUrl(url)) {
|
|
566
|
+
flushBuffer();
|
|
567
|
+
out.push(
|
|
568
|
+
/* @__PURE__ */ jsx4(
|
|
569
|
+
"a",
|
|
570
|
+
{
|
|
571
|
+
href: url,
|
|
572
|
+
target: "_blank",
|
|
573
|
+
rel: "noopener noreferrer",
|
|
574
|
+
style: { color: ctx.linkColor, textDecoration: "underline" },
|
|
575
|
+
children: renderInlineText(label, ctx)
|
|
576
|
+
},
|
|
577
|
+
ctx.nextKey()
|
|
578
|
+
)
|
|
579
|
+
);
|
|
580
|
+
i = closeUrl + 1;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (closeLabel !== -1 && ctx.sources && ctx.sources.length > 0) {
|
|
586
|
+
const body = text.slice(i + 1, closeLabel).trim();
|
|
587
|
+
const parts = body.split(/\s*,\s*/).filter((s) => s.length > 0);
|
|
588
|
+
const indices = parts.map((p) => Number(p));
|
|
589
|
+
const allValid = parts.length > 0 && indices.every(
|
|
590
|
+
(n) => Number.isInteger(n) && n >= 1 && n <= (ctx.sources?.length ?? 0)
|
|
591
|
+
);
|
|
592
|
+
if (allValid) {
|
|
593
|
+
flushBuffer();
|
|
594
|
+
out.push(
|
|
595
|
+
/* @__PURE__ */ jsx4("sup", { style: { whiteSpace: "nowrap" }, children: indices.map((n, idx) => {
|
|
596
|
+
const src = ctx.sources[n - 1];
|
|
597
|
+
const content = /* @__PURE__ */ jsxs2(
|
|
598
|
+
"span",
|
|
599
|
+
{
|
|
600
|
+
style: {
|
|
601
|
+
fontSize: "0.75em",
|
|
602
|
+
color: ctx.linkColor,
|
|
603
|
+
cursor: src.url ? "pointer" : "default"
|
|
604
|
+
},
|
|
605
|
+
title: src.heading ? `${src.title} \u2014 ${src.heading}` : src.title,
|
|
606
|
+
children: [
|
|
607
|
+
"[",
|
|
608
|
+
n,
|
|
609
|
+
"]"
|
|
610
|
+
]
|
|
611
|
+
}
|
|
612
|
+
);
|
|
613
|
+
const separator = idx > 0 ? " " : null;
|
|
614
|
+
return /* @__PURE__ */ jsxs2("span", { children: [
|
|
615
|
+
separator,
|
|
616
|
+
src.url ? /* @__PURE__ */ jsx4(
|
|
617
|
+
"a",
|
|
618
|
+
{
|
|
619
|
+
href: src.url,
|
|
620
|
+
target: "_blank",
|
|
621
|
+
rel: "noopener noreferrer",
|
|
622
|
+
style: {
|
|
623
|
+
color: ctx.linkColor,
|
|
624
|
+
textDecoration: "none"
|
|
625
|
+
},
|
|
626
|
+
children: content
|
|
627
|
+
}
|
|
628
|
+
) : content
|
|
629
|
+
] }, `${ctx.keyRoot}-cit-${idx}`);
|
|
630
|
+
}) }, ctx.nextKey())
|
|
631
|
+
);
|
|
632
|
+
i = closeLabel + 1;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
buffer += c;
|
|
638
|
+
i++;
|
|
639
|
+
}
|
|
640
|
+
flushBuffer();
|
|
641
|
+
return out;
|
|
642
|
+
}
|
|
643
|
+
function isSafeUrl(url) {
|
|
644
|
+
if (/^https?:\/\//i.test(url)) return true;
|
|
645
|
+
if (/^mailto:/i.test(url)) return true;
|
|
646
|
+
if (/^tel:/i.test(url)) return true;
|
|
647
|
+
if (/^\/[^/]/.test(url) || /^#/.test(url)) return true;
|
|
648
|
+
return false;
|
|
649
|
+
}
|
|
650
|
+
function renderMarkdown(source, opts = {}) {
|
|
651
|
+
const blocks = parseBlocks(source);
|
|
652
|
+
const linkColor = opts.linkColor ?? "#6C3CE1";
|
|
653
|
+
let keyCounter = 0;
|
|
654
|
+
const ctx = {
|
|
655
|
+
sources: opts.sources,
|
|
656
|
+
linkColor,
|
|
657
|
+
keyRoot: "md",
|
|
658
|
+
nextKey: () => `md-${keyCounter++}`
|
|
659
|
+
};
|
|
660
|
+
const renderInline = (line) => renderInlineText(line, ctx);
|
|
661
|
+
return /* @__PURE__ */ jsx4(Fragment, { children: blocks.map((block, idx) => {
|
|
662
|
+
const key = `b-${idx}`;
|
|
663
|
+
switch (block.kind) {
|
|
664
|
+
case "heading": {
|
|
665
|
+
const level = block.level ?? 1;
|
|
666
|
+
const style = {
|
|
667
|
+
margin: "8px 0 4px",
|
|
668
|
+
fontWeight: 600,
|
|
669
|
+
fontSize: level <= 2 ? "1.15em" : level === 3 ? "1.05em" : "1em"
|
|
670
|
+
};
|
|
671
|
+
const children = renderInline(block.lines[0] ?? "");
|
|
672
|
+
if (level === 1)
|
|
673
|
+
return /* @__PURE__ */ jsx4("h1", { style, children }, key);
|
|
674
|
+
if (level === 2)
|
|
675
|
+
return /* @__PURE__ */ jsx4("h2", { style, children }, key);
|
|
676
|
+
if (level === 3)
|
|
677
|
+
return /* @__PURE__ */ jsx4("h3", { style, children }, key);
|
|
678
|
+
if (level === 4)
|
|
679
|
+
return /* @__PURE__ */ jsx4("h4", { style, children }, key);
|
|
680
|
+
if (level === 5)
|
|
681
|
+
return /* @__PURE__ */ jsx4("h5", { style, children }, key);
|
|
682
|
+
return /* @__PURE__ */ jsx4("h6", { style, children }, key);
|
|
683
|
+
}
|
|
684
|
+
case "paragraph":
|
|
685
|
+
return /* @__PURE__ */ jsx4("p", { style: { margin: "0 0 8px", lineHeight: 1.5 }, children: renderInline(block.lines.join("\n")) }, key);
|
|
686
|
+
case "ul":
|
|
687
|
+
return /* @__PURE__ */ jsx4(
|
|
688
|
+
"ul",
|
|
689
|
+
{
|
|
690
|
+
style: {
|
|
691
|
+
margin: "0 0 8px",
|
|
692
|
+
paddingLeft: 20,
|
|
693
|
+
lineHeight: 1.5
|
|
694
|
+
},
|
|
695
|
+
children: block.lines.map((l, i) => /* @__PURE__ */ jsx4("li", { children: renderInline(l) }, i))
|
|
696
|
+
},
|
|
697
|
+
key
|
|
698
|
+
);
|
|
699
|
+
case "ol":
|
|
700
|
+
return /* @__PURE__ */ jsx4(
|
|
701
|
+
"ol",
|
|
702
|
+
{
|
|
703
|
+
style: {
|
|
704
|
+
margin: "0 0 8px",
|
|
705
|
+
paddingLeft: 22,
|
|
706
|
+
lineHeight: 1.5
|
|
707
|
+
},
|
|
708
|
+
children: block.lines.map((l, i) => /* @__PURE__ */ jsx4("li", { children: renderInline(l) }, i))
|
|
709
|
+
},
|
|
710
|
+
key
|
|
711
|
+
);
|
|
712
|
+
case "code":
|
|
713
|
+
return /* @__PURE__ */ jsx4(
|
|
714
|
+
"pre",
|
|
715
|
+
{
|
|
716
|
+
style: {
|
|
717
|
+
margin: "0 0 8px",
|
|
718
|
+
padding: "10px 12px",
|
|
719
|
+
background: "rgba(0,0,0,0.06)",
|
|
720
|
+
borderRadius: 6,
|
|
721
|
+
overflowX: "auto",
|
|
722
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
723
|
+
fontSize: "0.88em",
|
|
724
|
+
lineHeight: 1.45
|
|
725
|
+
},
|
|
726
|
+
children: /* @__PURE__ */ jsx4("code", { children: block.lines.join("\n") })
|
|
727
|
+
},
|
|
728
|
+
key
|
|
729
|
+
);
|
|
730
|
+
case "blockquote":
|
|
731
|
+
return /* @__PURE__ */ jsx4(
|
|
732
|
+
"blockquote",
|
|
733
|
+
{
|
|
734
|
+
style: {
|
|
735
|
+
margin: "0 0 8px",
|
|
736
|
+
padding: "4px 0 4px 10px",
|
|
737
|
+
borderLeft: "3px solid rgba(0,0,0,0.15)",
|
|
738
|
+
color: "rgba(0,0,0,0.75)",
|
|
739
|
+
fontStyle: "italic"
|
|
740
|
+
},
|
|
741
|
+
children: renderInline(block.lines.join("\n"))
|
|
742
|
+
},
|
|
743
|
+
key
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
}) });
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/components/action-confirmation-card.tsx
|
|
750
|
+
import { useState as useState4 } from "react";
|
|
751
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
752
|
+
function ActionConfirmationCard({
|
|
753
|
+
block,
|
|
754
|
+
primaryColor,
|
|
755
|
+
t,
|
|
756
|
+
onApprove,
|
|
757
|
+
onCancel
|
|
758
|
+
}) {
|
|
759
|
+
const [chosen, setChosen] = useState4(null);
|
|
760
|
+
const [showSpinner, setShowSpinner] = useState4(false);
|
|
761
|
+
const [error, setError] = useState4(null);
|
|
762
|
+
const [showSummary, setShowSummary] = useState4(false);
|
|
763
|
+
const handle = async (choice) => {
|
|
764
|
+
if (chosen) return;
|
|
765
|
+
setChosen(choice);
|
|
766
|
+
setError(null);
|
|
767
|
+
const spinnerTimer = window.setTimeout(() => setShowSpinner(true), 800);
|
|
768
|
+
try {
|
|
769
|
+
const fn = choice === "approve" ? onApprove : onCancel;
|
|
770
|
+
await fn(block.pendingToolCallId);
|
|
771
|
+
} catch (e) {
|
|
772
|
+
const msg = e instanceof Error ? e.message : t("action_failed");
|
|
773
|
+
setError(msg);
|
|
774
|
+
setChosen(null);
|
|
775
|
+
} finally {
|
|
776
|
+
window.clearTimeout(spinnerTimer);
|
|
777
|
+
setShowSpinner(false);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
const cardStyle = {
|
|
781
|
+
marginTop: 6,
|
|
782
|
+
padding: 12,
|
|
783
|
+
borderRadius: 12,
|
|
784
|
+
border: "1px solid #e0e0e0",
|
|
785
|
+
background: "#fff",
|
|
786
|
+
display: "flex",
|
|
787
|
+
flexDirection: "column",
|
|
788
|
+
gap: 8,
|
|
789
|
+
fontSize: 13,
|
|
790
|
+
lineHeight: 1.4
|
|
791
|
+
};
|
|
792
|
+
const titleStyle = {
|
|
793
|
+
fontWeight: 600,
|
|
794
|
+
color: "#222"
|
|
795
|
+
};
|
|
796
|
+
const disclosureBtn = {
|
|
797
|
+
background: "none",
|
|
798
|
+
border: "none",
|
|
799
|
+
padding: 0,
|
|
800
|
+
color: primaryColor,
|
|
801
|
+
fontSize: 12,
|
|
802
|
+
cursor: "pointer",
|
|
803
|
+
textAlign: "left",
|
|
804
|
+
fontFamily: "inherit"
|
|
805
|
+
};
|
|
806
|
+
const summaryStyle = {
|
|
807
|
+
color: "#555",
|
|
808
|
+
background: "#fafafa",
|
|
809
|
+
padding: 8,
|
|
810
|
+
borderRadius: 8,
|
|
811
|
+
whiteSpace: "pre-wrap"
|
|
812
|
+
};
|
|
813
|
+
const buttonRow = {
|
|
814
|
+
display: "flex",
|
|
815
|
+
gap: 8,
|
|
816
|
+
marginTop: 4
|
|
817
|
+
};
|
|
818
|
+
const baseBtn = (variant) => ({
|
|
819
|
+
flex: 1,
|
|
820
|
+
padding: "8px 12px",
|
|
821
|
+
borderRadius: 8,
|
|
822
|
+
fontSize: 13,
|
|
823
|
+
fontWeight: 500,
|
|
824
|
+
cursor: chosen ? "default" : "pointer",
|
|
825
|
+
border: variant === "primary" ? `1px solid ${primaryColor}` : "1px solid #ddd",
|
|
826
|
+
background: variant === "primary" ? primaryColor : "#fff",
|
|
827
|
+
color: variant === "primary" ? "#fff" : "#333",
|
|
828
|
+
opacity: chosen && chosen !== (variant === "primary" ? "approve" : "cancel") ? 0.5 : 1,
|
|
829
|
+
transition: "opacity 0.15s",
|
|
830
|
+
display: "flex",
|
|
831
|
+
alignItems: "center",
|
|
832
|
+
justifyContent: "center",
|
|
833
|
+
gap: 6,
|
|
834
|
+
fontFamily: "inherit"
|
|
835
|
+
});
|
|
836
|
+
return /* @__PURE__ */ jsxs3("div", { style: cardStyle, role: "group", "aria-label": block.title, children: [
|
|
837
|
+
/* @__PURE__ */ jsx5("div", { style: titleStyle, children: block.title }),
|
|
838
|
+
block.summary && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
839
|
+
/* @__PURE__ */ jsxs3(
|
|
840
|
+
"button",
|
|
841
|
+
{
|
|
842
|
+
type: "button",
|
|
843
|
+
style: disclosureBtn,
|
|
844
|
+
onClick: () => setShowSummary((s) => !s),
|
|
845
|
+
"aria-expanded": showSummary,
|
|
846
|
+
children: [
|
|
847
|
+
showSummary ? "\u25BE" : "\u25B8",
|
|
848
|
+
" ",
|
|
849
|
+
t("action_what_will_happen")
|
|
850
|
+
]
|
|
851
|
+
}
|
|
852
|
+
),
|
|
853
|
+
showSummary && /* @__PURE__ */ jsx5("div", { style: summaryStyle, children: block.summary })
|
|
854
|
+
] }),
|
|
855
|
+
/* @__PURE__ */ jsxs3("div", { style: buttonRow, children: [
|
|
856
|
+
/* @__PURE__ */ jsx5(
|
|
857
|
+
"button",
|
|
858
|
+
{
|
|
859
|
+
type: "button",
|
|
860
|
+
style: baseBtn("ghost"),
|
|
861
|
+
disabled: chosen !== null,
|
|
862
|
+
onClick: () => handle("cancel"),
|
|
863
|
+
children: chosen === "cancel" && showSpinner ? /* @__PURE__ */ jsx5(Spinner, { color: "#333" }) : t("action_cancel")
|
|
864
|
+
}
|
|
865
|
+
),
|
|
866
|
+
/* @__PURE__ */ jsx5(
|
|
867
|
+
"button",
|
|
868
|
+
{
|
|
869
|
+
type: "button",
|
|
870
|
+
style: baseBtn("primary"),
|
|
871
|
+
disabled: chosen !== null,
|
|
872
|
+
onClick: () => handle("approve"),
|
|
873
|
+
children: chosen === "approve" && showSpinner ? /* @__PURE__ */ jsx5(Spinner, { color: "#fff" }) : t("action_approve")
|
|
874
|
+
}
|
|
875
|
+
)
|
|
876
|
+
] }),
|
|
877
|
+
error && /* @__PURE__ */ jsx5("div", { role: "alert", style: { color: "#b91c1c", fontSize: 12 }, children: error })
|
|
878
|
+
] });
|
|
879
|
+
}
|
|
880
|
+
function Spinner({ color }) {
|
|
881
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
882
|
+
/* @__PURE__ */ jsx5("style", { children: `@keyframes ch-spin { to { transform: rotate(360deg); } }` }),
|
|
883
|
+
/* @__PURE__ */ jsx5(
|
|
884
|
+
"span",
|
|
885
|
+
{
|
|
886
|
+
"aria-hidden": "true",
|
|
887
|
+
style: {
|
|
888
|
+
width: 14,
|
|
889
|
+
height: 14,
|
|
890
|
+
borderRadius: "50%",
|
|
891
|
+
border: `2px solid ${color}`,
|
|
892
|
+
borderTopColor: "transparent",
|
|
893
|
+
animation: "ch-spin 0.8s linear infinite",
|
|
894
|
+
display: "inline-block"
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
)
|
|
898
|
+
] });
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/components/chat-messages.tsx
|
|
902
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
903
|
+
function MessageStatusPill({
|
|
904
|
+
status,
|
|
905
|
+
t
|
|
906
|
+
}) {
|
|
907
|
+
const failed = status === "failed";
|
|
908
|
+
const containerStyle = {
|
|
909
|
+
display: "flex",
|
|
910
|
+
alignItems: "center",
|
|
911
|
+
gap: 4,
|
|
912
|
+
marginTop: 2,
|
|
913
|
+
fontSize: 11,
|
|
914
|
+
color: failed ? "#b91c1c" : "#888",
|
|
915
|
+
alignSelf: "flex-end"
|
|
916
|
+
};
|
|
917
|
+
const labelKey = status === "sending" ? "status_sending" : status === "sent" ? "status_sent" : "status_failed";
|
|
918
|
+
return /* @__PURE__ */ jsxs4("div", { style: containerStyle, "aria-live": "polite", children: [
|
|
919
|
+
status === "sending" && /* @__PURE__ */ jsx6(ClockIcon, {}),
|
|
920
|
+
status === "sent" && /* @__PURE__ */ jsx6(CheckIcon, {}),
|
|
921
|
+
status === "failed" && /* @__PURE__ */ jsx6(ExclamationIcon, {}),
|
|
922
|
+
/* @__PURE__ */ jsx6("span", { children: t(labelKey) })
|
|
923
|
+
] });
|
|
924
|
+
}
|
|
925
|
+
function ClockIcon() {
|
|
926
|
+
return /* @__PURE__ */ jsxs4(
|
|
927
|
+
"svg",
|
|
928
|
+
{
|
|
929
|
+
width: "11",
|
|
930
|
+
height: "11",
|
|
931
|
+
viewBox: "0 0 24 24",
|
|
932
|
+
fill: "none",
|
|
933
|
+
stroke: "currentColor",
|
|
934
|
+
strokeWidth: "2",
|
|
935
|
+
strokeLinecap: "round",
|
|
936
|
+
strokeLinejoin: "round",
|
|
937
|
+
"aria-hidden": "true",
|
|
938
|
+
children: [
|
|
939
|
+
/* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "10" }),
|
|
940
|
+
/* @__PURE__ */ jsx6("polyline", { points: "12 6 12 12 16 14" })
|
|
941
|
+
]
|
|
942
|
+
}
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
function CheckIcon() {
|
|
946
|
+
return /* @__PURE__ */ jsx6(
|
|
947
|
+
"svg",
|
|
948
|
+
{
|
|
949
|
+
width: "11",
|
|
950
|
+
height: "11",
|
|
951
|
+
viewBox: "0 0 24 24",
|
|
952
|
+
fill: "none",
|
|
953
|
+
stroke: "currentColor",
|
|
954
|
+
strokeWidth: "2.5",
|
|
955
|
+
strokeLinecap: "round",
|
|
956
|
+
strokeLinejoin: "round",
|
|
957
|
+
"aria-hidden": "true",
|
|
958
|
+
children: /* @__PURE__ */ jsx6("polyline", { points: "20 6 9 17 4 12" })
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
function ExclamationIcon() {
|
|
963
|
+
return /* @__PURE__ */ jsxs4(
|
|
964
|
+
"svg",
|
|
965
|
+
{
|
|
966
|
+
width: "11",
|
|
967
|
+
height: "11",
|
|
968
|
+
viewBox: "0 0 24 24",
|
|
969
|
+
fill: "none",
|
|
970
|
+
stroke: "currentColor",
|
|
971
|
+
strokeWidth: "2",
|
|
972
|
+
strokeLinecap: "round",
|
|
973
|
+
strokeLinejoin: "round",
|
|
974
|
+
"aria-hidden": "true",
|
|
975
|
+
children: [
|
|
976
|
+
/* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "10" }),
|
|
977
|
+
/* @__PURE__ */ jsx6("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
978
|
+
/* @__PURE__ */ jsx6("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
979
|
+
]
|
|
980
|
+
}
|
|
981
|
+
);
|
|
982
|
+
}
|
|
374
983
|
function MessageRatingButtons({
|
|
375
984
|
messageId,
|
|
376
985
|
onRate,
|
|
@@ -378,7 +987,7 @@ function MessageRatingButtons({
|
|
|
378
987
|
t,
|
|
379
988
|
reduced
|
|
380
989
|
}) {
|
|
381
|
-
const [rated, setRated] =
|
|
990
|
+
const [rated, setRated] = useState5(null);
|
|
382
991
|
const handleRate = (rating) => {
|
|
383
992
|
setRated(rating);
|
|
384
993
|
onRate(messageId, rating);
|
|
@@ -396,15 +1005,16 @@ function MessageRatingButtons({
|
|
|
396
1005
|
transition: reduced ? "color 0.15s" : "all 0.15s",
|
|
397
1006
|
transform: isRated && !reduced ? "scale(1.15)" : "scale(1)"
|
|
398
1007
|
});
|
|
399
|
-
return /* @__PURE__ */
|
|
400
|
-
/* @__PURE__ */
|
|
1008
|
+
return /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: 4, marginTop: 4 }, children: [
|
|
1009
|
+
/* @__PURE__ */ jsx6(
|
|
401
1010
|
"button",
|
|
402
1011
|
{
|
|
403
1012
|
onClick: () => handleRate("positive"),
|
|
404
1013
|
disabled: rated !== null,
|
|
405
1014
|
style: buttonStyle(rated === "positive"),
|
|
406
1015
|
title: t("helpful"),
|
|
407
|
-
|
|
1016
|
+
"aria-label": t("helpful"),
|
|
1017
|
+
children: /* @__PURE__ */ jsxs4(
|
|
408
1018
|
"svg",
|
|
409
1019
|
{
|
|
410
1020
|
width: "14",
|
|
@@ -416,21 +1026,22 @@ function MessageRatingButtons({
|
|
|
416
1026
|
strokeLinecap: "round",
|
|
417
1027
|
strokeLinejoin: "round",
|
|
418
1028
|
children: [
|
|
419
|
-
/* @__PURE__ */
|
|
420
|
-
/* @__PURE__ */
|
|
1029
|
+
/* @__PURE__ */ jsx6("path", { d: "M7 10v12" }),
|
|
1030
|
+
/* @__PURE__ */ jsx6("path", { d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z" })
|
|
421
1031
|
]
|
|
422
1032
|
}
|
|
423
1033
|
)
|
|
424
1034
|
}
|
|
425
1035
|
),
|
|
426
|
-
/* @__PURE__ */
|
|
1036
|
+
/* @__PURE__ */ jsx6(
|
|
427
1037
|
"button",
|
|
428
1038
|
{
|
|
429
1039
|
onClick: () => handleRate("negative"),
|
|
430
1040
|
disabled: rated !== null,
|
|
431
1041
|
style: buttonStyle(rated === "negative"),
|
|
432
1042
|
title: t("not_helpful"),
|
|
433
|
-
|
|
1043
|
+
"aria-label": t("not_helpful"),
|
|
1044
|
+
children: /* @__PURE__ */ jsxs4(
|
|
434
1045
|
"svg",
|
|
435
1046
|
{
|
|
436
1047
|
width: "14",
|
|
@@ -442,8 +1053,8 @@ function MessageRatingButtons({
|
|
|
442
1053
|
strokeLinecap: "round",
|
|
443
1054
|
strokeLinejoin: "round",
|
|
444
1055
|
children: [
|
|
445
|
-
/* @__PURE__ */
|
|
446
|
-
/* @__PURE__ */
|
|
1056
|
+
/* @__PURE__ */ jsx6("path", { d: "M17 14V2" }),
|
|
1057
|
+
/* @__PURE__ */ jsx6("path", { d: "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22h0a3.13 3.13 0 0 1-3-3.88Z" })
|
|
447
1058
|
]
|
|
448
1059
|
}
|
|
449
1060
|
)
|
|
@@ -457,7 +1068,7 @@ function AnimatedMessage({
|
|
|
457
1068
|
animate,
|
|
458
1069
|
reduced
|
|
459
1070
|
}) {
|
|
460
|
-
const [visible, setVisible] =
|
|
1071
|
+
const [visible, setVisible] = useState5(!animate);
|
|
461
1072
|
useEffect5(() => {
|
|
462
1073
|
if (animate && !reduced) {
|
|
463
1074
|
const id = requestAnimationFrame(() => setVisible(true));
|
|
@@ -471,16 +1082,123 @@ function AnimatedMessage({
|
|
|
471
1082
|
transform: visible ? "translateX(0)" : `translateX(${isUser ? "12px" : "-12px"})`,
|
|
472
1083
|
transition: animate && !reduced ? "opacity 0.2s ease, transform 0.2s ease" : "none"
|
|
473
1084
|
};
|
|
474
|
-
return /* @__PURE__ */
|
|
1085
|
+
return /* @__PURE__ */ jsx6("div", { style, children });
|
|
1086
|
+
}
|
|
1087
|
+
function ChipRow({
|
|
1088
|
+
options,
|
|
1089
|
+
onSelect,
|
|
1090
|
+
primaryColor,
|
|
1091
|
+
reduced
|
|
1092
|
+
}) {
|
|
1093
|
+
const chip = {
|
|
1094
|
+
background: "none",
|
|
1095
|
+
border: "1px solid #e0e0e0",
|
|
1096
|
+
borderRadius: 20,
|
|
1097
|
+
padding: "6px 12px",
|
|
1098
|
+
fontSize: 13,
|
|
1099
|
+
color: "#333",
|
|
1100
|
+
cursor: "pointer",
|
|
1101
|
+
textAlign: "left",
|
|
1102
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
1103
|
+
transition: reduced ? "none" : "border-color 0.15s, background 0.15s"
|
|
1104
|
+
};
|
|
1105
|
+
return /* @__PURE__ */ jsx6(
|
|
1106
|
+
"div",
|
|
1107
|
+
{
|
|
1108
|
+
style: {
|
|
1109
|
+
display: "flex",
|
|
1110
|
+
flexWrap: "wrap",
|
|
1111
|
+
gap: 6,
|
|
1112
|
+
marginTop: 6
|
|
1113
|
+
},
|
|
1114
|
+
children: options.map((text) => /* @__PURE__ */ jsx6(
|
|
1115
|
+
"button",
|
|
1116
|
+
{
|
|
1117
|
+
style: chip,
|
|
1118
|
+
onClick: () => onSelect(text),
|
|
1119
|
+
onMouseEnter: (e) => {
|
|
1120
|
+
e.currentTarget.style.borderColor = primaryColor;
|
|
1121
|
+
e.currentTarget.style.background = `${primaryColor}08`;
|
|
1122
|
+
},
|
|
1123
|
+
onMouseLeave: (e) => {
|
|
1124
|
+
e.currentTarget.style.borderColor = "#e0e0e0";
|
|
1125
|
+
e.currentTarget.style.background = "none";
|
|
1126
|
+
},
|
|
1127
|
+
children: text
|
|
1128
|
+
},
|
|
1129
|
+
text
|
|
1130
|
+
))
|
|
1131
|
+
}
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
function BlockRenderer({
|
|
1135
|
+
block,
|
|
1136
|
+
onSend,
|
|
1137
|
+
onApproveAction,
|
|
1138
|
+
onCancelAction,
|
|
1139
|
+
primaryColor,
|
|
1140
|
+
reduced,
|
|
1141
|
+
t
|
|
1142
|
+
}) {
|
|
1143
|
+
switch (block.type) {
|
|
1144
|
+
case "quick_replies":
|
|
1145
|
+
return /* @__PURE__ */ jsx6(
|
|
1146
|
+
ChipRow,
|
|
1147
|
+
{
|
|
1148
|
+
options: block.options,
|
|
1149
|
+
onSelect: onSend,
|
|
1150
|
+
primaryColor,
|
|
1151
|
+
reduced
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
case "action_confirmation":
|
|
1155
|
+
return /* @__PURE__ */ jsx6(
|
|
1156
|
+
ActionConfirmationCard,
|
|
1157
|
+
{
|
|
1158
|
+
block,
|
|
1159
|
+
primaryColor,
|
|
1160
|
+
t,
|
|
1161
|
+
onApprove: onApproveAction,
|
|
1162
|
+
onCancel: onCancelAction
|
|
1163
|
+
}
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
function StreamingCursor({ reduced }) {
|
|
1168
|
+
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1169
|
+
/* @__PURE__ */ jsx6("style", { children: `@keyframes ch-caret-blink {
|
|
1170
|
+
0%, 50% { opacity: 1; }
|
|
1171
|
+
50.01%, 100% { opacity: 0; }
|
|
1172
|
+
}` }),
|
|
1173
|
+
/* @__PURE__ */ jsx6(
|
|
1174
|
+
"span",
|
|
1175
|
+
{
|
|
1176
|
+
"aria-hidden": "true",
|
|
1177
|
+
style: {
|
|
1178
|
+
display: "inline-block",
|
|
1179
|
+
width: 2,
|
|
1180
|
+
height: "1em",
|
|
1181
|
+
marginLeft: 2,
|
|
1182
|
+
verticalAlign: "text-bottom",
|
|
1183
|
+
background: "#777",
|
|
1184
|
+
animation: reduced ? "none" : "ch-caret-blink 1s step-start infinite"
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
)
|
|
1188
|
+
] });
|
|
475
1189
|
}
|
|
476
1190
|
function Message({
|
|
477
1191
|
message,
|
|
478
1192
|
config,
|
|
479
1193
|
onRate,
|
|
1194
|
+
onSend,
|
|
1195
|
+
onApproveAction,
|
|
1196
|
+
onCancelAction,
|
|
480
1197
|
hasConversation,
|
|
481
1198
|
t,
|
|
482
1199
|
animate,
|
|
483
|
-
reduced
|
|
1200
|
+
reduced,
|
|
1201
|
+
showSuggestions
|
|
484
1202
|
}) {
|
|
485
1203
|
const isUser = message.role === "user";
|
|
486
1204
|
const bubbleStyle = {
|
|
@@ -499,9 +1217,39 @@ function Message({
|
|
|
499
1217
|
borderBottomLeftRadius: 4
|
|
500
1218
|
}
|
|
501
1219
|
};
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
1220
|
+
const linkColor = isUser ? "#ffffff" : config.primaryColor;
|
|
1221
|
+
return /* @__PURE__ */ jsxs4(AnimatedMessage, { isUser, animate, reduced, children: [
|
|
1222
|
+
/* @__PURE__ */ jsx6("div", { style: bubbleStyle, children: isUser ? message.content : /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1223
|
+
renderMarkdown(message.content, {
|
|
1224
|
+
sources: message.sources,
|
|
1225
|
+
linkColor
|
|
1226
|
+
}),
|
|
1227
|
+
message.streaming && /* @__PURE__ */ jsx6(StreamingCursor, { reduced })
|
|
1228
|
+
] }) }),
|
|
1229
|
+
isUser && message.status && /* @__PURE__ */ jsx6(MessageStatusPill, { status: message.status, t }),
|
|
1230
|
+
!isUser && message.blocks?.map((block, i) => /* @__PURE__ */ jsx6(
|
|
1231
|
+
BlockRenderer,
|
|
1232
|
+
{
|
|
1233
|
+
block,
|
|
1234
|
+
onSend,
|
|
1235
|
+
onApproveAction,
|
|
1236
|
+
onCancelAction,
|
|
1237
|
+
primaryColor: config.primaryColor,
|
|
1238
|
+
reduced,
|
|
1239
|
+
t
|
|
1240
|
+
},
|
|
1241
|
+
i
|
|
1242
|
+
)),
|
|
1243
|
+
!isUser && showSuggestions && message.suggestions?.length ? /* @__PURE__ */ jsx6(
|
|
1244
|
+
ChipRow,
|
|
1245
|
+
{
|
|
1246
|
+
options: message.suggestions,
|
|
1247
|
+
onSelect: onSend,
|
|
1248
|
+
primaryColor: config.primaryColor,
|
|
1249
|
+
reduced
|
|
1250
|
+
}
|
|
1251
|
+
) : null,
|
|
1252
|
+
message.role === "bot" && message.id && hasConversation && !message.streaming && !message.blocks?.some((b) => b.type === "action_confirmation") && /* @__PURE__ */ jsx6(
|
|
505
1253
|
MessageRatingButtons,
|
|
506
1254
|
{
|
|
507
1255
|
messageId: message.id,
|
|
@@ -521,12 +1269,12 @@ function TypingDots({ reduced }) {
|
|
|
521
1269
|
background: "#999",
|
|
522
1270
|
animation: reduced ? "none" : `ch-dot-pulse 1.2s ease-in-out ${delay}s infinite`
|
|
523
1271
|
});
|
|
524
|
-
return /* @__PURE__ */
|
|
525
|
-
/* @__PURE__ */
|
|
1272
|
+
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1273
|
+
/* @__PURE__ */ jsx6("style", { children: `@keyframes ch-dot-pulse {
|
|
526
1274
|
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
|
527
1275
|
40% { opacity: 1; transform: scale(1); }
|
|
528
1276
|
}` }),
|
|
529
|
-
/* @__PURE__ */
|
|
1277
|
+
/* @__PURE__ */ jsxs4(
|
|
530
1278
|
"div",
|
|
531
1279
|
{
|
|
532
1280
|
style: {
|
|
@@ -540,16 +1288,27 @@ function TypingDots({ reduced }) {
|
|
|
540
1288
|
alignItems: "center"
|
|
541
1289
|
},
|
|
542
1290
|
children: [
|
|
543
|
-
/* @__PURE__ */
|
|
544
|
-
/* @__PURE__ */
|
|
545
|
-
/* @__PURE__ */
|
|
1291
|
+
/* @__PURE__ */ jsx6("div", { style: dotStyle(0) }),
|
|
1292
|
+
/* @__PURE__ */ jsx6("div", { style: dotStyle(0.2) }),
|
|
1293
|
+
/* @__PURE__ */ jsx6("div", { style: dotStyle(0.4) })
|
|
546
1294
|
]
|
|
547
1295
|
}
|
|
548
1296
|
)
|
|
549
1297
|
] });
|
|
550
1298
|
}
|
|
551
1299
|
function ChatMessages() {
|
|
552
|
-
const {
|
|
1300
|
+
const {
|
|
1301
|
+
messages,
|
|
1302
|
+
isLoading,
|
|
1303
|
+
error,
|
|
1304
|
+
config,
|
|
1305
|
+
conversationId,
|
|
1306
|
+
rateMessage,
|
|
1307
|
+
sendMessage,
|
|
1308
|
+
approveAction,
|
|
1309
|
+
cancelAction,
|
|
1310
|
+
t
|
|
1311
|
+
} = useChat();
|
|
553
1312
|
const reduced = useReducedMotion();
|
|
554
1313
|
const messagesEndRef = useRef3(null);
|
|
555
1314
|
const isFirstRender = useRef3(true);
|
|
@@ -566,6 +1325,13 @@ function ChatMessages() {
|
|
|
566
1325
|
useEffect5(() => {
|
|
567
1326
|
prevMessageCount.current = messages.length;
|
|
568
1327
|
}, [messages.length]);
|
|
1328
|
+
let lastBotIndex = -1;
|
|
1329
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1330
|
+
if (messages[i].role === "bot") {
|
|
1331
|
+
lastBotIndex = i;
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
569
1335
|
const containerStyle = {
|
|
570
1336
|
flex: 1,
|
|
571
1337
|
overflowY: "auto",
|
|
@@ -574,24 +1340,31 @@ function ChatMessages() {
|
|
|
574
1340
|
flexDirection: "column",
|
|
575
1341
|
gap: 12
|
|
576
1342
|
};
|
|
577
|
-
|
|
578
|
-
|
|
1343
|
+
const lastMsg = messages[messages.length - 1];
|
|
1344
|
+
const waitingForFirstToken = isLoading && (lastMsg?.role !== "bot" || lastMsg.streaming !== true);
|
|
1345
|
+
return /* @__PURE__ */ jsxs4("div", { style: containerStyle, children: [
|
|
1346
|
+
messages.map((msg, i) => /* @__PURE__ */ jsx6(
|
|
579
1347
|
Message,
|
|
580
1348
|
{
|
|
581
1349
|
message: msg,
|
|
582
1350
|
config,
|
|
583
1351
|
onRate: rateMessage,
|
|
1352
|
+
onSend: sendMessage,
|
|
1353
|
+
onApproveAction: approveAction,
|
|
1354
|
+
onCancelAction: cancelAction,
|
|
584
1355
|
hasConversation: conversationId !== null,
|
|
585
1356
|
t,
|
|
586
1357
|
animate: i >= newStartIndex,
|
|
587
|
-
reduced
|
|
1358
|
+
reduced,
|
|
1359
|
+
showSuggestions: i === lastBotIndex && !isLoading && msg.streaming !== true
|
|
588
1360
|
},
|
|
589
1361
|
i
|
|
590
1362
|
)),
|
|
591
|
-
|
|
592
|
-
error && /* @__PURE__ */
|
|
1363
|
+
waitingForFirstToken && /* @__PURE__ */ jsx6(TypingDots, { reduced }),
|
|
1364
|
+
error && /* @__PURE__ */ jsx6(
|
|
593
1365
|
"div",
|
|
594
1366
|
{
|
|
1367
|
+
role: "alert",
|
|
595
1368
|
style: {
|
|
596
1369
|
alignSelf: "flex-start",
|
|
597
1370
|
padding: "10px 14px",
|
|
@@ -604,12 +1377,12 @@ function ChatMessages() {
|
|
|
604
1377
|
children: error
|
|
605
1378
|
}
|
|
606
1379
|
),
|
|
607
|
-
/* @__PURE__ */
|
|
1380
|
+
/* @__PURE__ */ jsx6("div", { ref: messagesEndRef })
|
|
608
1381
|
] });
|
|
609
1382
|
}
|
|
610
1383
|
|
|
611
1384
|
// src/components/chat-suggestions.tsx
|
|
612
|
-
import { jsx as
|
|
1385
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
613
1386
|
function ChatSuggestions() {
|
|
614
1387
|
const { messages, isLoading, config, sendMessage } = useChat();
|
|
615
1388
|
const reduced = useReducedMotion();
|
|
@@ -636,7 +1409,7 @@ function ChatSuggestions() {
|
|
|
636
1409
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
637
1410
|
transition: reduced ? "none" : "border-color 0.15s, background 0.15s"
|
|
638
1411
|
};
|
|
639
|
-
return /* @__PURE__ */
|
|
1412
|
+
return /* @__PURE__ */ jsx7("div", { style: containerStyle, children: config.suggestedMessages.map((text) => /* @__PURE__ */ jsx7(
|
|
640
1413
|
"button",
|
|
641
1414
|
{
|
|
642
1415
|
style: chipStyle,
|
|
@@ -656,15 +1429,75 @@ function ChatSuggestions() {
|
|
|
656
1429
|
}
|
|
657
1430
|
|
|
658
1431
|
// src/components/chat-input.tsx
|
|
659
|
-
import {
|
|
660
|
-
|
|
1432
|
+
import {
|
|
1433
|
+
useEffect as useEffect6,
|
|
1434
|
+
useState as useState6
|
|
1435
|
+
} from "react";
|
|
1436
|
+
import {
|
|
1437
|
+
ScreenshotCancelled,
|
|
1438
|
+
canCaptureScreenshot,
|
|
1439
|
+
captureScreenshot
|
|
1440
|
+
} from "@customerhero/js";
|
|
1441
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1442
|
+
var MAX_ATTACHMENTS = 3;
|
|
661
1443
|
function ChatInput() {
|
|
662
|
-
const { sendMessage, isLoading, config, t } = useChat();
|
|
1444
|
+
const { sendMessage, uploadAttachment, isLoading, config, t } = useChat();
|
|
663
1445
|
const reduced = useReducedMotion();
|
|
664
|
-
const [value, setValue] =
|
|
1446
|
+
const [value, setValue] = useState6("");
|
|
1447
|
+
const [attachments, setAttachments] = useState6([]);
|
|
1448
|
+
const [captureSupported, setCaptureSupported] = useState6(false);
|
|
1449
|
+
useEffect6(() => {
|
|
1450
|
+
setCaptureSupported(canCaptureScreenshot());
|
|
1451
|
+
}, []);
|
|
1452
|
+
useEffect6(() => {
|
|
1453
|
+
return () => {
|
|
1454
|
+
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1455
|
+
};
|
|
1456
|
+
}, []);
|
|
1457
|
+
const updateAttachment = (id, patch) => {
|
|
1458
|
+
setAttachments(
|
|
1459
|
+
(current) => current.map((a) => a.id === id ? { ...a, ...patch } : a)
|
|
1460
|
+
);
|
|
1461
|
+
};
|
|
1462
|
+
const startUpload = async (blob) => {
|
|
1463
|
+
if (attachments.length >= MAX_ATTACHMENTS) return;
|
|
1464
|
+
const id = `att_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1465
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
1466
|
+
setAttachments((current) => [
|
|
1467
|
+
...current,
|
|
1468
|
+
{ id, status: "uploading", previewUrl, blob }
|
|
1469
|
+
]);
|
|
1470
|
+
try {
|
|
1471
|
+
const { attachmentToken } = await uploadAttachment(blob);
|
|
1472
|
+
updateAttachment(id, {
|
|
1473
|
+
status: "ready",
|
|
1474
|
+
token: attachmentToken
|
|
1475
|
+
});
|
|
1476
|
+
} catch {
|
|
1477
|
+
updateAttachment(id, { status: "error" });
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
const handleCapture = async () => {
|
|
1481
|
+
try {
|
|
1482
|
+
const blob = await captureScreenshot();
|
|
1483
|
+
await startUpload(blob);
|
|
1484
|
+
} catch (e) {
|
|
1485
|
+
if (e instanceof ScreenshotCancelled) return;
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
const handleRemove = (id) => {
|
|
1489
|
+
setAttachments((current) => {
|
|
1490
|
+
const target = current.find((a) => a.id === id);
|
|
1491
|
+
if (target) URL.revokeObjectURL(target.previewUrl);
|
|
1492
|
+
return current.filter((a) => a.id !== id);
|
|
1493
|
+
});
|
|
1494
|
+
};
|
|
1495
|
+
const readyTokens = attachments.filter((a) => a.status === "ready").map((a) => a.token);
|
|
665
1496
|
const handleSend = () => {
|
|
666
1497
|
if (!value.trim() || isLoading) return;
|
|
667
|
-
sendMessage(value);
|
|
1498
|
+
sendMessage(value, readyTokens.length > 0 ? { attachmentTokens: readyTokens } : void 0);
|
|
1499
|
+
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1500
|
+
setAttachments([]);
|
|
668
1501
|
setValue("");
|
|
669
1502
|
};
|
|
670
1503
|
const handleKeyDown = (e) => {
|
|
@@ -676,6 +1509,11 @@ function ChatInput() {
|
|
|
676
1509
|
const containerStyle = {
|
|
677
1510
|
padding: "12px 16px",
|
|
678
1511
|
borderTop: "1px solid #eee",
|
|
1512
|
+
display: "flex",
|
|
1513
|
+
flexDirection: "column",
|
|
1514
|
+
gap: 8
|
|
1515
|
+
};
|
|
1516
|
+
const rowStyle = {
|
|
679
1517
|
display: "flex",
|
|
680
1518
|
alignItems: "center",
|
|
681
1519
|
gap: 8
|
|
@@ -691,7 +1529,7 @@ function ChatInput() {
|
|
|
691
1529
|
color: config.textColor,
|
|
692
1530
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
693
1531
|
};
|
|
694
|
-
const
|
|
1532
|
+
const sendButtonStyle = {
|
|
695
1533
|
width: 36,
|
|
696
1534
|
height: 36,
|
|
697
1535
|
borderRadius: "50%",
|
|
@@ -707,57 +1545,210 @@ function ChatInput() {
|
|
|
707
1545
|
flexShrink: 0,
|
|
708
1546
|
transform: "scale(1)"
|
|
709
1547
|
};
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1548
|
+
const iconButtonStyle = (disabled) => ({
|
|
1549
|
+
width: 36,
|
|
1550
|
+
height: 36,
|
|
1551
|
+
borderRadius: "50%",
|
|
1552
|
+
background: "transparent",
|
|
1553
|
+
border: "none",
|
|
1554
|
+
color: disabled ? "#ccc" : "#666",
|
|
1555
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1556
|
+
display: "flex",
|
|
1557
|
+
alignItems: "center",
|
|
1558
|
+
justifyContent: "center",
|
|
1559
|
+
flexShrink: 0,
|
|
1560
|
+
padding: 0
|
|
1561
|
+
});
|
|
1562
|
+
const captureDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
|
|
1563
|
+
return /* @__PURE__ */ jsxs5("div", { style: containerStyle, children: [
|
|
1564
|
+
attachments.length > 0 && /* @__PURE__ */ jsx8(
|
|
1565
|
+
"div",
|
|
713
1566
|
{
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1567
|
+
style: { display: "flex", gap: 8, flexWrap: "wrap" },
|
|
1568
|
+
"aria-label": "Attachments",
|
|
1569
|
+
children: attachments.map((a) => /* @__PURE__ */ jsx8(
|
|
1570
|
+
Thumbnail,
|
|
1571
|
+
{
|
|
1572
|
+
attachment: a,
|
|
1573
|
+
onRemove: () => handleRemove(a.id),
|
|
1574
|
+
t
|
|
1575
|
+
},
|
|
1576
|
+
a.id
|
|
1577
|
+
))
|
|
721
1578
|
}
|
|
722
1579
|
),
|
|
723
|
-
/* @__PURE__ */
|
|
1580
|
+
/* @__PURE__ */ jsxs5("div", { style: rowStyle, children: [
|
|
1581
|
+
captureSupported && /* @__PURE__ */ jsx8(
|
|
1582
|
+
"button",
|
|
1583
|
+
{
|
|
1584
|
+
type: "button",
|
|
1585
|
+
onClick: handleCapture,
|
|
1586
|
+
disabled: captureDisabled,
|
|
1587
|
+
style: iconButtonStyle(captureDisabled),
|
|
1588
|
+
"aria-label": t("screenshot_capture"),
|
|
1589
|
+
title: t("screenshot_capture"),
|
|
1590
|
+
children: /* @__PURE__ */ jsx8(CameraIcon, {})
|
|
1591
|
+
}
|
|
1592
|
+
),
|
|
1593
|
+
/* @__PURE__ */ jsx8(
|
|
1594
|
+
"input",
|
|
1595
|
+
{
|
|
1596
|
+
type: "text",
|
|
1597
|
+
value,
|
|
1598
|
+
onChange: (e) => setValue(e.target.value),
|
|
1599
|
+
onKeyDown: handleKeyDown,
|
|
1600
|
+
placeholder: config.placeholderText,
|
|
1601
|
+
style: inputStyle,
|
|
1602
|
+
disabled: isLoading
|
|
1603
|
+
}
|
|
1604
|
+
),
|
|
1605
|
+
/* @__PURE__ */ jsx8(
|
|
1606
|
+
"button",
|
|
1607
|
+
{
|
|
1608
|
+
onClick: handleSend,
|
|
1609
|
+
disabled: isLoading || !value.trim(),
|
|
1610
|
+
style: sendButtonStyle,
|
|
1611
|
+
"aria-label": t("send_message"),
|
|
1612
|
+
onMouseEnter: (e) => {
|
|
1613
|
+
if (!reduced && !isLoading)
|
|
1614
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
1615
|
+
},
|
|
1616
|
+
onMouseLeave: (e) => {
|
|
1617
|
+
if (!reduced) e.currentTarget.style.transform = "scale(1)";
|
|
1618
|
+
},
|
|
1619
|
+
children: /* @__PURE__ */ jsxs5(
|
|
1620
|
+
"svg",
|
|
1621
|
+
{
|
|
1622
|
+
width: "16",
|
|
1623
|
+
height: "16",
|
|
1624
|
+
viewBox: "0 0 24 24",
|
|
1625
|
+
fill: "none",
|
|
1626
|
+
stroke: "currentColor",
|
|
1627
|
+
strokeWidth: "2",
|
|
1628
|
+
strokeLinecap: "round",
|
|
1629
|
+
strokeLinejoin: "round",
|
|
1630
|
+
children: [
|
|
1631
|
+
/* @__PURE__ */ jsx8("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
1632
|
+
/* @__PURE__ */ jsx8("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
1633
|
+
]
|
|
1634
|
+
}
|
|
1635
|
+
)
|
|
1636
|
+
}
|
|
1637
|
+
)
|
|
1638
|
+
] })
|
|
1639
|
+
] });
|
|
1640
|
+
}
|
|
1641
|
+
function Thumbnail({
|
|
1642
|
+
attachment,
|
|
1643
|
+
onRemove,
|
|
1644
|
+
t
|
|
1645
|
+
}) {
|
|
1646
|
+
const { status, previewUrl } = attachment;
|
|
1647
|
+
const wrap = {
|
|
1648
|
+
position: "relative",
|
|
1649
|
+
width: 56,
|
|
1650
|
+
height: 56,
|
|
1651
|
+
borderRadius: 8,
|
|
1652
|
+
overflow: "hidden",
|
|
1653
|
+
border: status === "error" ? "2px solid #b91c1c" : "1px solid #e0e0e0",
|
|
1654
|
+
background: "#f5f5f5"
|
|
1655
|
+
};
|
|
1656
|
+
const img = {
|
|
1657
|
+
width: "100%",
|
|
1658
|
+
height: "100%",
|
|
1659
|
+
objectFit: "cover"
|
|
1660
|
+
};
|
|
1661
|
+
const removeBtn = {
|
|
1662
|
+
position: "absolute",
|
|
1663
|
+
top: 2,
|
|
1664
|
+
right: 2,
|
|
1665
|
+
width: 18,
|
|
1666
|
+
height: 18,
|
|
1667
|
+
borderRadius: "50%",
|
|
1668
|
+
border: "none",
|
|
1669
|
+
background: "rgba(0,0,0,0.6)",
|
|
1670
|
+
color: "white",
|
|
1671
|
+
fontSize: 11,
|
|
1672
|
+
lineHeight: "18px",
|
|
1673
|
+
padding: 0,
|
|
1674
|
+
cursor: "pointer",
|
|
1675
|
+
textAlign: "center"
|
|
1676
|
+
};
|
|
1677
|
+
const overlay = {
|
|
1678
|
+
position: "absolute",
|
|
1679
|
+
inset: 0,
|
|
1680
|
+
background: "rgba(255,255,255,0.7)",
|
|
1681
|
+
display: "flex",
|
|
1682
|
+
alignItems: "center",
|
|
1683
|
+
justifyContent: "center"
|
|
1684
|
+
};
|
|
1685
|
+
return /* @__PURE__ */ jsxs5("div", { style: wrap, children: [
|
|
1686
|
+
/* @__PURE__ */ jsx8("img", { src: previewUrl, alt: "", style: img }),
|
|
1687
|
+
status === "uploading" && /* @__PURE__ */ jsx8("div", { style: overlay, "aria-label": "Uploading", children: /* @__PURE__ */ jsx8(Spinner2, {}) }),
|
|
1688
|
+
status === "error" && /* @__PURE__ */ jsx8(
|
|
1689
|
+
"div",
|
|
1690
|
+
{
|
|
1691
|
+
style: { ...overlay, background: "rgba(255,255,255,0.85)" },
|
|
1692
|
+
"aria-label": t("status_failed"),
|
|
1693
|
+
title: t("status_failed"),
|
|
1694
|
+
children: /* @__PURE__ */ jsx8("span", { style: { color: "#b91c1c", fontSize: 11, fontWeight: 600 }, children: "!" })
|
|
1695
|
+
}
|
|
1696
|
+
),
|
|
1697
|
+
/* @__PURE__ */ jsx8(
|
|
724
1698
|
"button",
|
|
725
1699
|
{
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
"aria-label": t("
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1700
|
+
type: "button",
|
|
1701
|
+
style: removeBtn,
|
|
1702
|
+
onClick: onRemove,
|
|
1703
|
+
"aria-label": t("attachment_remove"),
|
|
1704
|
+
children: "\xD7"
|
|
1705
|
+
}
|
|
1706
|
+
)
|
|
1707
|
+
] });
|
|
1708
|
+
}
|
|
1709
|
+
function CameraIcon() {
|
|
1710
|
+
return /* @__PURE__ */ jsxs5(
|
|
1711
|
+
"svg",
|
|
1712
|
+
{
|
|
1713
|
+
width: "20",
|
|
1714
|
+
height: "20",
|
|
1715
|
+
viewBox: "0 0 24 24",
|
|
1716
|
+
fill: "none",
|
|
1717
|
+
stroke: "currentColor",
|
|
1718
|
+
strokeWidth: "2",
|
|
1719
|
+
strokeLinecap: "round",
|
|
1720
|
+
strokeLinejoin: "round",
|
|
1721
|
+
"aria-hidden": "true",
|
|
1722
|
+
children: [
|
|
1723
|
+
/* @__PURE__ */ jsx8("path", { d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" }),
|
|
1724
|
+
/* @__PURE__ */ jsx8("circle", { cx: "12", cy: "13", r: "4" })
|
|
1725
|
+
]
|
|
1726
|
+
}
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
function Spinner2() {
|
|
1730
|
+
return /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
1731
|
+
/* @__PURE__ */ jsx8("style", { children: `@keyframes ch-att-spin { to { transform: rotate(360deg); } }` }),
|
|
1732
|
+
/* @__PURE__ */ jsx8(
|
|
1733
|
+
"span",
|
|
1734
|
+
{
|
|
1735
|
+
"aria-hidden": "true",
|
|
1736
|
+
style: {
|
|
1737
|
+
width: 16,
|
|
1738
|
+
height: 16,
|
|
1739
|
+
borderRadius: "50%",
|
|
1740
|
+
border: "2px solid #888",
|
|
1741
|
+
borderTopColor: "transparent",
|
|
1742
|
+
animation: "ch-att-spin 0.8s linear infinite",
|
|
1743
|
+
display: "inline-block"
|
|
1744
|
+
}
|
|
754
1745
|
}
|
|
755
1746
|
)
|
|
756
1747
|
] });
|
|
757
1748
|
}
|
|
758
1749
|
|
|
759
1750
|
// src/components/chat-window.tsx
|
|
760
|
-
import { Fragment as
|
|
1751
|
+
import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
761
1752
|
function ConfigError({ message, title }) {
|
|
762
1753
|
const errorStyle = {
|
|
763
1754
|
flex: 1,
|
|
@@ -769,8 +1760,8 @@ function ConfigError({ message, title }) {
|
|
|
769
1760
|
textAlign: "center",
|
|
770
1761
|
gap: 8
|
|
771
1762
|
};
|
|
772
|
-
return /* @__PURE__ */
|
|
773
|
-
/* @__PURE__ */
|
|
1763
|
+
return /* @__PURE__ */ jsxs6("div", { style: errorStyle, children: [
|
|
1764
|
+
/* @__PURE__ */ jsxs6(
|
|
774
1765
|
"svg",
|
|
775
1766
|
{
|
|
776
1767
|
width: "32",
|
|
@@ -782,22 +1773,22 @@ function ConfigError({ message, title }) {
|
|
|
782
1773
|
strokeLinecap: "round",
|
|
783
1774
|
strokeLinejoin: "round",
|
|
784
1775
|
children: [
|
|
785
|
-
/* @__PURE__ */
|
|
786
|
-
/* @__PURE__ */
|
|
787
|
-
/* @__PURE__ */
|
|
1776
|
+
/* @__PURE__ */ jsx9("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1777
|
+
/* @__PURE__ */ jsx9("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
1778
|
+
/* @__PURE__ */ jsx9("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
788
1779
|
]
|
|
789
1780
|
}
|
|
790
1781
|
),
|
|
791
|
-
/* @__PURE__ */
|
|
792
|
-
/* @__PURE__ */
|
|
1782
|
+
/* @__PURE__ */ jsx9("p", { style: { fontSize: 14, fontWeight: 500, color: "#333", margin: 0 }, children: title }),
|
|
1783
|
+
/* @__PURE__ */ jsx9("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
|
|
793
1784
|
] });
|
|
794
1785
|
}
|
|
795
1786
|
function ChatWindow() {
|
|
796
|
-
const { isOpen, config, configError, t } = useChat();
|
|
1787
|
+
const { isOpen, config, configError, t, isRtl } = useChat();
|
|
797
1788
|
const reduced = useReducedMotion();
|
|
798
|
-
const [visible, setVisible] =
|
|
799
|
-
const [shouldRender, setShouldRender] =
|
|
800
|
-
|
|
1789
|
+
const [visible, setVisible] = useState7(false);
|
|
1790
|
+
const [shouldRender, setShouldRender] = useState7(false);
|
|
1791
|
+
useEffect7(() => {
|
|
801
1792
|
if (isOpen) {
|
|
802
1793
|
setShouldRender(true);
|
|
803
1794
|
requestAnimationFrame(() => {
|
|
@@ -814,10 +1805,11 @@ function ChatWindow() {
|
|
|
814
1805
|
}
|
|
815
1806
|
}, [isOpen, reduced]);
|
|
816
1807
|
if (!shouldRender) return null;
|
|
1808
|
+
const effectivePosition = isRtl ? config.position === "bottom-right" ? "bottom-left" : "bottom-right" : config.position;
|
|
817
1809
|
const style = {
|
|
818
1810
|
position: "fixed",
|
|
819
1811
|
bottom: 90,
|
|
820
|
-
[
|
|
1812
|
+
[effectivePosition === "bottom-left" ? "left" : "right"]: 20,
|
|
821
1813
|
width: 380,
|
|
822
1814
|
maxWidth: "calc(100vw - 40px)",
|
|
823
1815
|
height: 520,
|
|
@@ -845,17 +1837,17 @@ function ChatWindow() {
|
|
|
845
1837
|
textDecoration: "underline",
|
|
846
1838
|
textUnderlineOffset: 2
|
|
847
1839
|
};
|
|
848
|
-
return /* @__PURE__ */
|
|
849
|
-
/* @__PURE__ */
|
|
850
|
-
configError ? /* @__PURE__ */
|
|
851
|
-
/* @__PURE__ */
|
|
852
|
-
/* @__PURE__ */
|
|
853
|
-
/* @__PURE__ */
|
|
1840
|
+
return /* @__PURE__ */ jsxs6("div", { style, dir: isRtl ? "rtl" : "ltr", children: [
|
|
1841
|
+
/* @__PURE__ */ jsx9(ChatHeader, {}),
|
|
1842
|
+
configError ? /* @__PURE__ */ jsx9(ConfigError, { title: t("unable_to_load"), message: configError }) : /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
1843
|
+
/* @__PURE__ */ jsx9(ChatMessages, {}),
|
|
1844
|
+
/* @__PURE__ */ jsx9(ChatSuggestions, {}),
|
|
1845
|
+
/* @__PURE__ */ jsx9(ChatInput, {})
|
|
854
1846
|
] }),
|
|
855
|
-
/* @__PURE__ */
|
|
1847
|
+
/* @__PURE__ */ jsxs6("div", { style: poweredStyle, children: [
|
|
856
1848
|
t("powered_by"),
|
|
857
1849
|
" ",
|
|
858
|
-
/* @__PURE__ */
|
|
1850
|
+
/* @__PURE__ */ jsx9(
|
|
859
1851
|
"a",
|
|
860
1852
|
{
|
|
861
1853
|
href: "https://customerhero.app",
|
|
@@ -870,11 +1862,11 @@ function ChatWindow() {
|
|
|
870
1862
|
}
|
|
871
1863
|
|
|
872
1864
|
// src/components/chat-widget.tsx
|
|
873
|
-
import { Fragment as
|
|
1865
|
+
import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
874
1866
|
function ChatWidgetInner({ identity }) {
|
|
875
1867
|
const client = useCustomerHeroClient();
|
|
876
1868
|
const prevIdentityRef = useRef4(void 0);
|
|
877
|
-
|
|
1869
|
+
useEffect8(() => {
|
|
878
1870
|
const key = identity ? JSON.stringify(identity) : void 0;
|
|
879
1871
|
if (key !== prevIdentityRef.current) {
|
|
880
1872
|
prevIdentityRef.current = key;
|
|
@@ -883,14 +1875,16 @@ function ChatWidgetInner({ identity }) {
|
|
|
883
1875
|
}
|
|
884
1876
|
}
|
|
885
1877
|
}, [identity, client]);
|
|
886
|
-
return /* @__PURE__ */
|
|
887
|
-
/* @__PURE__ */
|
|
888
|
-
/* @__PURE__ */
|
|
1878
|
+
return /* @__PURE__ */ jsxs7(Fragment6, { children: [
|
|
1879
|
+
/* @__PURE__ */ jsx10(ChatBubble, {}),
|
|
1880
|
+
/* @__PURE__ */ jsx10(ChatWindow, {})
|
|
889
1881
|
] });
|
|
890
1882
|
}
|
|
891
1883
|
function ChatWidget({ identity, ...config }) {
|
|
892
|
-
return /* @__PURE__ */
|
|
1884
|
+
return /* @__PURE__ */ jsx10(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ jsx10(ChatWidgetInner, { identity }) });
|
|
893
1885
|
}
|
|
894
1886
|
export {
|
|
895
|
-
|
|
1887
|
+
ActionConfirmationCard,
|
|
1888
|
+
ChatWidget,
|
|
1889
|
+
useChat
|
|
896
1890
|
};
|