@customerhero/react 0.0.2 → 1.0.1
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 +1119 -121
- package/dist/index.d.cts +36 -3
- package/dist/index.d.ts +36 -3
- package/dist/index.js +1116 -113
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -20,12 +20,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
23
|
+
ActionConfirmationCard: () => ActionConfirmationCard,
|
|
24
|
+
ChatWidget: () => ChatWidget,
|
|
25
|
+
useChat: () => useChat
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(index_exports);
|
|
26
28
|
|
|
27
29
|
// src/components/chat-widget.tsx
|
|
28
|
-
var
|
|
30
|
+
var import_react10 = require("react");
|
|
29
31
|
|
|
30
32
|
// src/context.tsx
|
|
31
33
|
var import_react = require("react");
|
|
@@ -71,13 +73,26 @@ function useChat() {
|
|
|
71
73
|
...state,
|
|
72
74
|
t: client.t,
|
|
73
75
|
sendMessage: (0, import_react2.useCallback)(
|
|
74
|
-
(message) => client.sendMessage(message),
|
|
76
|
+
(message, options) => client.sendMessage(message, options),
|
|
77
|
+
[client]
|
|
78
|
+
),
|
|
79
|
+
uploadAttachment: (0, import_react2.useCallback)(
|
|
80
|
+
(blob, options) => client.uploadAttachment(blob, options),
|
|
75
81
|
[client]
|
|
76
82
|
),
|
|
77
83
|
rateMessage: (0, import_react2.useCallback)(
|
|
78
84
|
(messageId, rating) => client.rateMessage(messageId, rating),
|
|
79
85
|
[client]
|
|
80
86
|
),
|
|
87
|
+
approveAction: (0, import_react2.useCallback)(
|
|
88
|
+
(pendingId) => client.approveAction(pendingId),
|
|
89
|
+
[client]
|
|
90
|
+
),
|
|
91
|
+
cancelAction: (0, import_react2.useCallback)(
|
|
92
|
+
(pendingId) => client.cancelAction(pendingId),
|
|
93
|
+
[client]
|
|
94
|
+
),
|
|
95
|
+
setLocale: (0, import_react2.useCallback)((tag) => client.setLocale(tag), [client]),
|
|
81
96
|
toggle: (0, import_react2.useCallback)(() => client.toggle(), [client]),
|
|
82
97
|
open: (0, import_react2.useCallback)(() => client.open(), [client]),
|
|
83
98
|
close: (0, import_react2.useCallback)(() => client.close(), [client]),
|
|
@@ -108,7 +123,7 @@ function useReducedMotion() {
|
|
|
108
123
|
// src/components/chat-bubble.tsx
|
|
109
124
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
110
125
|
function ChatBubble() {
|
|
111
|
-
const { toggle, config, t } = useChat();
|
|
126
|
+
const { toggle, config, t, isRtl } = useChat();
|
|
112
127
|
const reduced = useReducedMotion();
|
|
113
128
|
const [mounted, setMounted] = (0, import_react4.useState)(false);
|
|
114
129
|
(0, import_react4.useEffect)(() => {
|
|
@@ -116,10 +131,11 @@ function ChatBubble() {
|
|
|
116
131
|
return () => cancelAnimationFrame(id);
|
|
117
132
|
}, []);
|
|
118
133
|
const visible = mounted;
|
|
134
|
+
const effectivePosition = isRtl ? config.position === "bottom-right" ? "bottom-left" : "bottom-right" : config.position;
|
|
119
135
|
const style = {
|
|
120
136
|
position: "fixed",
|
|
121
137
|
bottom: 20,
|
|
122
|
-
[
|
|
138
|
+
[effectivePosition === "bottom-left" ? "left" : "right"]: 20,
|
|
123
139
|
width: 56,
|
|
124
140
|
height: 56,
|
|
125
141
|
borderRadius: "50%",
|
|
@@ -143,6 +159,7 @@ function ChatBubble() {
|
|
|
143
159
|
{
|
|
144
160
|
onClick: toggle,
|
|
145
161
|
style,
|
|
162
|
+
dir: isRtl ? "rtl" : "ltr",
|
|
146
163
|
"aria-label": t("open_chat"),
|
|
147
164
|
onMouseEnter: (e) => {
|
|
148
165
|
if (!reduced) e.currentTarget.style.transform = "scale(1.1)";
|
|
@@ -169,7 +186,7 @@ function ChatBubble() {
|
|
|
169
186
|
}
|
|
170
187
|
|
|
171
188
|
// src/components/chat-window.tsx
|
|
172
|
-
var
|
|
189
|
+
var import_react9 = require("react");
|
|
173
190
|
|
|
174
191
|
// src/components/chat-header.tsx
|
|
175
192
|
var import_react5 = require("react");
|
|
@@ -388,8 +405,602 @@ function ChatHeader() {
|
|
|
388
405
|
}
|
|
389
406
|
|
|
390
407
|
// src/components/chat-messages.tsx
|
|
391
|
-
var
|
|
408
|
+
var import_react7 = require("react");
|
|
409
|
+
|
|
410
|
+
// src/markdown/render.tsx
|
|
392
411
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
412
|
+
function parseBlocks(source) {
|
|
413
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
414
|
+
const blocks = [];
|
|
415
|
+
let i = 0;
|
|
416
|
+
while (i < lines.length) {
|
|
417
|
+
const line = lines[i];
|
|
418
|
+
if (line.trim() === "") {
|
|
419
|
+
i++;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
const fenceMatch = line.match(/^```(.*)$/);
|
|
423
|
+
if (fenceMatch) {
|
|
424
|
+
const lang = fenceMatch[1].trim() || void 0;
|
|
425
|
+
const body2 = [];
|
|
426
|
+
i++;
|
|
427
|
+
while (i < lines.length && !/^```\s*$/.test(lines[i])) {
|
|
428
|
+
body2.push(lines[i]);
|
|
429
|
+
i++;
|
|
430
|
+
}
|
|
431
|
+
if (i < lines.length) i++;
|
|
432
|
+
blocks.push({ kind: "code", lines: body2, lang });
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.*?)\s*#*\s*$/);
|
|
436
|
+
if (headingMatch) {
|
|
437
|
+
blocks.push({
|
|
438
|
+
kind: "heading",
|
|
439
|
+
level: headingMatch[1].length,
|
|
440
|
+
lines: [headingMatch[2]]
|
|
441
|
+
});
|
|
442
|
+
i++;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (/^(\s*)[-*+]\s+/.test(line)) {
|
|
446
|
+
const body2 = [];
|
|
447
|
+
while (i < lines.length && /^(\s*)[-*+]\s+/.test(lines[i])) {
|
|
448
|
+
body2.push(lines[i].replace(/^(\s*)[-*+]\s+/, ""));
|
|
449
|
+
i++;
|
|
450
|
+
}
|
|
451
|
+
blocks.push({ kind: "ul", lines: body2 });
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (/^(\s*)\d+\.\s+/.test(line)) {
|
|
455
|
+
const body2 = [];
|
|
456
|
+
while (i < lines.length && /^(\s*)\d+\.\s+/.test(lines[i])) {
|
|
457
|
+
body2.push(lines[i].replace(/^(\s*)\d+\.\s+/, ""));
|
|
458
|
+
i++;
|
|
459
|
+
}
|
|
460
|
+
blocks.push({ kind: "ol", lines: body2 });
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (/^>\s?/.test(line)) {
|
|
464
|
+
const body2 = [];
|
|
465
|
+
while (i < lines.length && /^>\s?/.test(lines[i])) {
|
|
466
|
+
body2.push(lines[i].replace(/^>\s?/, ""));
|
|
467
|
+
i++;
|
|
468
|
+
}
|
|
469
|
+
blocks.push({ kind: "blockquote", lines: body2 });
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const body = [line];
|
|
473
|
+
i++;
|
|
474
|
+
while (i < lines.length) {
|
|
475
|
+
const next = lines[i];
|
|
476
|
+
if (next.trim() === "") break;
|
|
477
|
+
if (/^```/.test(next) || /^#{1,6}\s/.test(next) || /^(\s*)[-*+]\s+/.test(next) || /^(\s*)\d+\.\s+/.test(next) || /^>\s?/.test(next)) {
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
body.push(next);
|
|
481
|
+
i++;
|
|
482
|
+
}
|
|
483
|
+
blocks.push({ kind: "paragraph", lines: body });
|
|
484
|
+
}
|
|
485
|
+
return blocks;
|
|
486
|
+
}
|
|
487
|
+
function findClosing(text, from, end) {
|
|
488
|
+
let i = from;
|
|
489
|
+
while (i <= text.length - end.length) {
|
|
490
|
+
const c = text[i];
|
|
491
|
+
if (c === "\\") {
|
|
492
|
+
i += 2;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (text.startsWith(end, i)) return i;
|
|
496
|
+
i++;
|
|
497
|
+
}
|
|
498
|
+
return -1;
|
|
499
|
+
}
|
|
500
|
+
function renderInlineText(text, ctx) {
|
|
501
|
+
const out = [];
|
|
502
|
+
let buffer = "";
|
|
503
|
+
let i = 0;
|
|
504
|
+
const flushBuffer = () => {
|
|
505
|
+
if (buffer.length > 0) {
|
|
506
|
+
out.push(buffer);
|
|
507
|
+
buffer = "";
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
while (i < text.length) {
|
|
511
|
+
const c = text[i];
|
|
512
|
+
if (c === "\\" && i + 1 < text.length) {
|
|
513
|
+
buffer += text[i + 1];
|
|
514
|
+
i += 2;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (c === " " && text[i + 1] === " " && text[i + 2] === "\n") {
|
|
518
|
+
flushBuffer();
|
|
519
|
+
out.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("br", {}, ctx.nextKey()));
|
|
520
|
+
i += 3;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (c === "\n") {
|
|
524
|
+
buffer += " ";
|
|
525
|
+
i++;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (c === "`") {
|
|
529
|
+
const close = findClosing(text, i + 1, "`");
|
|
530
|
+
if (close !== -1) {
|
|
531
|
+
flushBuffer();
|
|
532
|
+
out.push(
|
|
533
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
534
|
+
"code",
|
|
535
|
+
{
|
|
536
|
+
style: {
|
|
537
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
538
|
+
fontSize: "0.92em",
|
|
539
|
+
background: "rgba(0,0,0,0.06)",
|
|
540
|
+
padding: "1px 4px",
|
|
541
|
+
borderRadius: 3
|
|
542
|
+
},
|
|
543
|
+
children: text.slice(i + 1, close)
|
|
544
|
+
},
|
|
545
|
+
ctx.nextKey()
|
|
546
|
+
)
|
|
547
|
+
);
|
|
548
|
+
i = close + 1;
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if ((text.startsWith("**", i) || text.startsWith("__", i)) && i + 2 < text.length) {
|
|
553
|
+
const marker = text.slice(i, i + 2);
|
|
554
|
+
const close = findClosing(text, i + 2, marker);
|
|
555
|
+
if (close !== -1 && close > i + 2) {
|
|
556
|
+
flushBuffer();
|
|
557
|
+
const inner = renderInlineText(text.slice(i + 2, close), ctx);
|
|
558
|
+
out.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: inner }, ctx.nextKey()));
|
|
559
|
+
i = close + 2;
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (c === "*" || c === "_") {
|
|
564
|
+
const prev = i === 0 ? " " : text[i - 1];
|
|
565
|
+
const isWordBoundaryBefore = c === "*" || !/\w/.test(prev);
|
|
566
|
+
if (isWordBoundaryBefore) {
|
|
567
|
+
const close = findClosing(text, i + 1, c);
|
|
568
|
+
const prevOfClose = close === -1 ? " " : close + 1 < text.length ? text[close + 1] : " ";
|
|
569
|
+
const isWordBoundaryAfter = c === "*" || !/\w/.test(prevOfClose);
|
|
570
|
+
if (close !== -1 && close > i + 1 && text[close + 1] !== c && isWordBoundaryAfter) {
|
|
571
|
+
flushBuffer();
|
|
572
|
+
const inner = renderInlineText(text.slice(i + 1, close), ctx);
|
|
573
|
+
out.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("em", { children: inner }, ctx.nextKey()));
|
|
574
|
+
i = close + 1;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (c === "[") {
|
|
580
|
+
const closeLabel = findClosing(text, i + 1, "]");
|
|
581
|
+
if (closeLabel !== -1 && text[closeLabel + 1] === "(") {
|
|
582
|
+
const closeUrl = findClosing(text, closeLabel + 2, ")");
|
|
583
|
+
if (closeUrl !== -1) {
|
|
584
|
+
const label = text.slice(i + 1, closeLabel);
|
|
585
|
+
const url = text.slice(closeLabel + 2, closeUrl).trim();
|
|
586
|
+
if (isSafeUrl(url)) {
|
|
587
|
+
flushBuffer();
|
|
588
|
+
out.push(
|
|
589
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
590
|
+
"a",
|
|
591
|
+
{
|
|
592
|
+
href: url,
|
|
593
|
+
target: "_blank",
|
|
594
|
+
rel: "noopener noreferrer",
|
|
595
|
+
style: { color: ctx.linkColor, textDecoration: "underline" },
|
|
596
|
+
children: renderInlineText(label, ctx)
|
|
597
|
+
},
|
|
598
|
+
ctx.nextKey()
|
|
599
|
+
)
|
|
600
|
+
);
|
|
601
|
+
i = closeUrl + 1;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (closeLabel !== -1 && ctx.sources && ctx.sources.length > 0) {
|
|
607
|
+
const body = text.slice(i + 1, closeLabel).trim();
|
|
608
|
+
const parts = body.split(/\s*,\s*/).filter((s) => s.length > 0);
|
|
609
|
+
const indices = parts.map((p) => Number(p));
|
|
610
|
+
const allValid = parts.length > 0 && indices.every(
|
|
611
|
+
(n) => Number.isInteger(n) && n >= 1 && n <= (ctx.sources?.length ?? 0)
|
|
612
|
+
);
|
|
613
|
+
if (allValid) {
|
|
614
|
+
flushBuffer();
|
|
615
|
+
out.push(
|
|
616
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("sup", { style: { whiteSpace: "nowrap" }, children: indices.map((n, idx) => {
|
|
617
|
+
const src = ctx.sources[n - 1];
|
|
618
|
+
const content = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
619
|
+
"span",
|
|
620
|
+
{
|
|
621
|
+
style: {
|
|
622
|
+
fontSize: "0.75em",
|
|
623
|
+
color: ctx.linkColor,
|
|
624
|
+
cursor: src.url ? "pointer" : "default"
|
|
625
|
+
},
|
|
626
|
+
title: src.heading ? `${src.title} \u2014 ${src.heading}` : src.title,
|
|
627
|
+
children: [
|
|
628
|
+
"[",
|
|
629
|
+
n,
|
|
630
|
+
"]"
|
|
631
|
+
]
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
const separator = idx > 0 ? " " : null;
|
|
635
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
636
|
+
separator,
|
|
637
|
+
src.url ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
638
|
+
"a",
|
|
639
|
+
{
|
|
640
|
+
href: src.url,
|
|
641
|
+
target: "_blank",
|
|
642
|
+
rel: "noopener noreferrer",
|
|
643
|
+
style: {
|
|
644
|
+
color: ctx.linkColor,
|
|
645
|
+
textDecoration: "none"
|
|
646
|
+
},
|
|
647
|
+
children: content
|
|
648
|
+
}
|
|
649
|
+
) : content
|
|
650
|
+
] }, `${ctx.keyRoot}-cit-${idx}`);
|
|
651
|
+
}) }, ctx.nextKey())
|
|
652
|
+
);
|
|
653
|
+
i = closeLabel + 1;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
buffer += c;
|
|
659
|
+
i++;
|
|
660
|
+
}
|
|
661
|
+
flushBuffer();
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
function isSafeUrl(url) {
|
|
665
|
+
if (/^https?:\/\//i.test(url)) return true;
|
|
666
|
+
if (/^mailto:/i.test(url)) return true;
|
|
667
|
+
if (/^tel:/i.test(url)) return true;
|
|
668
|
+
if (/^\/[^/]/.test(url) || /^#/.test(url)) return true;
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
function renderMarkdown(source, opts = {}) {
|
|
672
|
+
const blocks = parseBlocks(source);
|
|
673
|
+
const linkColor = opts.linkColor ?? "#6C3CE1";
|
|
674
|
+
let keyCounter = 0;
|
|
675
|
+
const ctx = {
|
|
676
|
+
sources: opts.sources,
|
|
677
|
+
linkColor,
|
|
678
|
+
keyRoot: "md",
|
|
679
|
+
nextKey: () => `md-${keyCounter++}`
|
|
680
|
+
};
|
|
681
|
+
const renderInline = (line) => renderInlineText(line, ctx);
|
|
682
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: blocks.map((block, idx) => {
|
|
683
|
+
const key = `b-${idx}`;
|
|
684
|
+
switch (block.kind) {
|
|
685
|
+
case "heading": {
|
|
686
|
+
const level = block.level ?? 1;
|
|
687
|
+
const style = {
|
|
688
|
+
margin: "8px 0 4px",
|
|
689
|
+
fontWeight: 600,
|
|
690
|
+
fontSize: level <= 2 ? "1.15em" : level === 3 ? "1.05em" : "1em"
|
|
691
|
+
};
|
|
692
|
+
const children = renderInline(block.lines[0] ?? "");
|
|
693
|
+
if (level === 1)
|
|
694
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { style, children }, key);
|
|
695
|
+
if (level === 2)
|
|
696
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { style, children }, key);
|
|
697
|
+
if (level === 3)
|
|
698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { style, children }, key);
|
|
699
|
+
if (level === 4)
|
|
700
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { style, children }, key);
|
|
701
|
+
if (level === 5)
|
|
702
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h5", { style, children }, key);
|
|
703
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h6", { style, children }, key);
|
|
704
|
+
}
|
|
705
|
+
case "paragraph":
|
|
706
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { margin: "0 0 8px", lineHeight: 1.5 }, children: renderInline(block.lines.join("\n")) }, key);
|
|
707
|
+
case "ul":
|
|
708
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
709
|
+
"ul",
|
|
710
|
+
{
|
|
711
|
+
style: {
|
|
712
|
+
margin: "0 0 8px",
|
|
713
|
+
paddingLeft: 20,
|
|
714
|
+
lineHeight: 1.5
|
|
715
|
+
},
|
|
716
|
+
children: block.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: renderInline(l) }, i))
|
|
717
|
+
},
|
|
718
|
+
key
|
|
719
|
+
);
|
|
720
|
+
case "ol":
|
|
721
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
722
|
+
"ol",
|
|
723
|
+
{
|
|
724
|
+
style: {
|
|
725
|
+
margin: "0 0 8px",
|
|
726
|
+
paddingLeft: 22,
|
|
727
|
+
lineHeight: 1.5
|
|
728
|
+
},
|
|
729
|
+
children: block.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: renderInline(l) }, i))
|
|
730
|
+
},
|
|
731
|
+
key
|
|
732
|
+
);
|
|
733
|
+
case "code":
|
|
734
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
735
|
+
"pre",
|
|
736
|
+
{
|
|
737
|
+
style: {
|
|
738
|
+
margin: "0 0 8px",
|
|
739
|
+
padding: "10px 12px",
|
|
740
|
+
background: "rgba(0,0,0,0.06)",
|
|
741
|
+
borderRadius: 6,
|
|
742
|
+
overflowX: "auto",
|
|
743
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
744
|
+
fontSize: "0.88em",
|
|
745
|
+
lineHeight: 1.45
|
|
746
|
+
},
|
|
747
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: block.lines.join("\n") })
|
|
748
|
+
},
|
|
749
|
+
key
|
|
750
|
+
);
|
|
751
|
+
case "blockquote":
|
|
752
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
753
|
+
"blockquote",
|
|
754
|
+
{
|
|
755
|
+
style: {
|
|
756
|
+
margin: "0 0 8px",
|
|
757
|
+
padding: "4px 0 4px 10px",
|
|
758
|
+
borderLeft: "3px solid rgba(0,0,0,0.15)",
|
|
759
|
+
color: "rgba(0,0,0,0.75)",
|
|
760
|
+
fontStyle: "italic"
|
|
761
|
+
},
|
|
762
|
+
children: renderInline(block.lines.join("\n"))
|
|
763
|
+
},
|
|
764
|
+
key
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
}) });
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/components/action-confirmation-card.tsx
|
|
771
|
+
var import_react6 = require("react");
|
|
772
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
773
|
+
function ActionConfirmationCard({
|
|
774
|
+
block,
|
|
775
|
+
primaryColor,
|
|
776
|
+
t,
|
|
777
|
+
onApprove,
|
|
778
|
+
onCancel
|
|
779
|
+
}) {
|
|
780
|
+
const [chosen, setChosen] = (0, import_react6.useState)(null);
|
|
781
|
+
const [showSpinner, setShowSpinner] = (0, import_react6.useState)(false);
|
|
782
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
783
|
+
const [showSummary, setShowSummary] = (0, import_react6.useState)(false);
|
|
784
|
+
const handle = async (choice) => {
|
|
785
|
+
if (chosen) return;
|
|
786
|
+
setChosen(choice);
|
|
787
|
+
setError(null);
|
|
788
|
+
const spinnerTimer = window.setTimeout(() => setShowSpinner(true), 800);
|
|
789
|
+
try {
|
|
790
|
+
const fn = choice === "approve" ? onApprove : onCancel;
|
|
791
|
+
await fn(block.pendingToolCallId);
|
|
792
|
+
} catch (e) {
|
|
793
|
+
const msg = e instanceof Error ? e.message : t("action_failed");
|
|
794
|
+
setError(msg);
|
|
795
|
+
setChosen(null);
|
|
796
|
+
} finally {
|
|
797
|
+
window.clearTimeout(spinnerTimer);
|
|
798
|
+
setShowSpinner(false);
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
const cardStyle = {
|
|
802
|
+
marginTop: 6,
|
|
803
|
+
padding: 12,
|
|
804
|
+
borderRadius: 12,
|
|
805
|
+
border: "1px solid #e0e0e0",
|
|
806
|
+
background: "#fff",
|
|
807
|
+
display: "flex",
|
|
808
|
+
flexDirection: "column",
|
|
809
|
+
gap: 8,
|
|
810
|
+
fontSize: 13,
|
|
811
|
+
lineHeight: 1.4
|
|
812
|
+
};
|
|
813
|
+
const titleStyle = {
|
|
814
|
+
fontWeight: 600,
|
|
815
|
+
color: "#222"
|
|
816
|
+
};
|
|
817
|
+
const disclosureBtn = {
|
|
818
|
+
background: "none",
|
|
819
|
+
border: "none",
|
|
820
|
+
padding: 0,
|
|
821
|
+
color: primaryColor,
|
|
822
|
+
fontSize: 12,
|
|
823
|
+
cursor: "pointer",
|
|
824
|
+
textAlign: "left",
|
|
825
|
+
fontFamily: "inherit"
|
|
826
|
+
};
|
|
827
|
+
const summaryStyle = {
|
|
828
|
+
color: "#555",
|
|
829
|
+
background: "#fafafa",
|
|
830
|
+
padding: 8,
|
|
831
|
+
borderRadius: 8,
|
|
832
|
+
whiteSpace: "pre-wrap"
|
|
833
|
+
};
|
|
834
|
+
const buttonRow = {
|
|
835
|
+
display: "flex",
|
|
836
|
+
gap: 8,
|
|
837
|
+
marginTop: 4
|
|
838
|
+
};
|
|
839
|
+
const baseBtn = (variant) => ({
|
|
840
|
+
flex: 1,
|
|
841
|
+
padding: "8px 12px",
|
|
842
|
+
borderRadius: 8,
|
|
843
|
+
fontSize: 13,
|
|
844
|
+
fontWeight: 500,
|
|
845
|
+
cursor: chosen ? "default" : "pointer",
|
|
846
|
+
border: variant === "primary" ? `1px solid ${primaryColor}` : "1px solid #ddd",
|
|
847
|
+
background: variant === "primary" ? primaryColor : "#fff",
|
|
848
|
+
color: variant === "primary" ? "#fff" : "#333",
|
|
849
|
+
opacity: chosen && chosen !== (variant === "primary" ? "approve" : "cancel") ? 0.5 : 1,
|
|
850
|
+
transition: "opacity 0.15s",
|
|
851
|
+
display: "flex",
|
|
852
|
+
alignItems: "center",
|
|
853
|
+
justifyContent: "center",
|
|
854
|
+
gap: 6,
|
|
855
|
+
fontFamily: "inherit"
|
|
856
|
+
});
|
|
857
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: cardStyle, role: "group", "aria-label": block.title, children: [
|
|
858
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: titleStyle, children: block.title }),
|
|
859
|
+
block.summary && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
860
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
861
|
+
"button",
|
|
862
|
+
{
|
|
863
|
+
type: "button",
|
|
864
|
+
style: disclosureBtn,
|
|
865
|
+
onClick: () => setShowSummary((s) => !s),
|
|
866
|
+
"aria-expanded": showSummary,
|
|
867
|
+
children: [
|
|
868
|
+
showSummary ? "\u25BE" : "\u25B8",
|
|
869
|
+
" ",
|
|
870
|
+
t("action_what_will_happen")
|
|
871
|
+
]
|
|
872
|
+
}
|
|
873
|
+
),
|
|
874
|
+
showSummary && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: summaryStyle, children: block.summary })
|
|
875
|
+
] }),
|
|
876
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: buttonRow, children: [
|
|
877
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
878
|
+
"button",
|
|
879
|
+
{
|
|
880
|
+
type: "button",
|
|
881
|
+
style: baseBtn("ghost"),
|
|
882
|
+
disabled: chosen !== null,
|
|
883
|
+
onClick: () => handle("cancel"),
|
|
884
|
+
children: chosen === "cancel" && showSpinner ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Spinner, { color: "#333" }) : t("action_cancel")
|
|
885
|
+
}
|
|
886
|
+
),
|
|
887
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
888
|
+
"button",
|
|
889
|
+
{
|
|
890
|
+
type: "button",
|
|
891
|
+
style: baseBtn("primary"),
|
|
892
|
+
disabled: chosen !== null,
|
|
893
|
+
onClick: () => handle("approve"),
|
|
894
|
+
children: chosen === "approve" && showSpinner ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Spinner, { color: "#fff" }) : t("action_approve")
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
] }),
|
|
898
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { role: "alert", style: { color: "#b91c1c", fontSize: 12 }, children: error })
|
|
899
|
+
] });
|
|
900
|
+
}
|
|
901
|
+
function Spinner({ color }) {
|
|
902
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
903
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes ch-spin { to { transform: rotate(360deg); } }` }),
|
|
904
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
905
|
+
"span",
|
|
906
|
+
{
|
|
907
|
+
"aria-hidden": "true",
|
|
908
|
+
style: {
|
|
909
|
+
width: 14,
|
|
910
|
+
height: 14,
|
|
911
|
+
borderRadius: "50%",
|
|
912
|
+
border: `2px solid ${color}`,
|
|
913
|
+
borderTopColor: "transparent",
|
|
914
|
+
animation: "ch-spin 0.8s linear infinite",
|
|
915
|
+
display: "inline-block"
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
)
|
|
919
|
+
] });
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/components/chat-messages.tsx
|
|
923
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
924
|
+
function MessageStatusPill({
|
|
925
|
+
status,
|
|
926
|
+
t
|
|
927
|
+
}) {
|
|
928
|
+
const failed = status === "failed";
|
|
929
|
+
const containerStyle = {
|
|
930
|
+
display: "flex",
|
|
931
|
+
alignItems: "center",
|
|
932
|
+
gap: 4,
|
|
933
|
+
marginTop: 2,
|
|
934
|
+
fontSize: 11,
|
|
935
|
+
color: failed ? "#b91c1c" : "#888",
|
|
936
|
+
alignSelf: "flex-end"
|
|
937
|
+
};
|
|
938
|
+
const labelKey = status === "sending" ? "status_sending" : status === "sent" ? "status_sent" : "status_failed";
|
|
939
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: containerStyle, "aria-live": "polite", children: [
|
|
940
|
+
status === "sending" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ClockIcon, {}),
|
|
941
|
+
status === "sent" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CheckIcon, {}),
|
|
942
|
+
status === "failed" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExclamationIcon, {}),
|
|
943
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: t(labelKey) })
|
|
944
|
+
] });
|
|
945
|
+
}
|
|
946
|
+
function ClockIcon() {
|
|
947
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
948
|
+
"svg",
|
|
949
|
+
{
|
|
950
|
+
width: "11",
|
|
951
|
+
height: "11",
|
|
952
|
+
viewBox: "0 0 24 24",
|
|
953
|
+
fill: "none",
|
|
954
|
+
stroke: "currentColor",
|
|
955
|
+
strokeWidth: "2",
|
|
956
|
+
strokeLinecap: "round",
|
|
957
|
+
strokeLinejoin: "round",
|
|
958
|
+
"aria-hidden": "true",
|
|
959
|
+
children: [
|
|
960
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
961
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "12 6 12 12 16 14" })
|
|
962
|
+
]
|
|
963
|
+
}
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
function CheckIcon() {
|
|
967
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
968
|
+
"svg",
|
|
969
|
+
{
|
|
970
|
+
width: "11",
|
|
971
|
+
height: "11",
|
|
972
|
+
viewBox: "0 0 24 24",
|
|
973
|
+
fill: "none",
|
|
974
|
+
stroke: "currentColor",
|
|
975
|
+
strokeWidth: "2.5",
|
|
976
|
+
strokeLinecap: "round",
|
|
977
|
+
strokeLinejoin: "round",
|
|
978
|
+
"aria-hidden": "true",
|
|
979
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "20 6 9 17 4 12" })
|
|
980
|
+
}
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
function ExclamationIcon() {
|
|
984
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
985
|
+
"svg",
|
|
986
|
+
{
|
|
987
|
+
width: "11",
|
|
988
|
+
height: "11",
|
|
989
|
+
viewBox: "0 0 24 24",
|
|
990
|
+
fill: "none",
|
|
991
|
+
stroke: "currentColor",
|
|
992
|
+
strokeWidth: "2",
|
|
993
|
+
strokeLinecap: "round",
|
|
994
|
+
strokeLinejoin: "round",
|
|
995
|
+
"aria-hidden": "true",
|
|
996
|
+
children: [
|
|
997
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
999
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
1000
|
+
]
|
|
1001
|
+
}
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
393
1004
|
function MessageRatingButtons({
|
|
394
1005
|
messageId,
|
|
395
1006
|
onRate,
|
|
@@ -397,7 +1008,7 @@ function MessageRatingButtons({
|
|
|
397
1008
|
t,
|
|
398
1009
|
reduced
|
|
399
1010
|
}) {
|
|
400
|
-
const [rated, setRated] = (0,
|
|
1011
|
+
const [rated, setRated] = (0, import_react7.useState)(null);
|
|
401
1012
|
const handleRate = (rating) => {
|
|
402
1013
|
setRated(rating);
|
|
403
1014
|
onRate(messageId, rating);
|
|
@@ -415,15 +1026,16 @@ function MessageRatingButtons({
|
|
|
415
1026
|
transition: reduced ? "color 0.15s" : "all 0.15s",
|
|
416
1027
|
transform: isRated && !reduced ? "scale(1.15)" : "scale(1)"
|
|
417
1028
|
});
|
|
418
|
-
return /* @__PURE__ */ (0,
|
|
419
|
-
/* @__PURE__ */ (0,
|
|
1029
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: 4, marginTop: 4 }, children: [
|
|
1030
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
420
1031
|
"button",
|
|
421
1032
|
{
|
|
422
1033
|
onClick: () => handleRate("positive"),
|
|
423
1034
|
disabled: rated !== null,
|
|
424
1035
|
style: buttonStyle(rated === "positive"),
|
|
425
1036
|
title: t("helpful"),
|
|
426
|
-
|
|
1037
|
+
"aria-label": t("helpful"),
|
|
1038
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
427
1039
|
"svg",
|
|
428
1040
|
{
|
|
429
1041
|
width: "14",
|
|
@@ -435,21 +1047,22 @@ function MessageRatingButtons({
|
|
|
435
1047
|
strokeLinecap: "round",
|
|
436
1048
|
strokeLinejoin: "round",
|
|
437
1049
|
children: [
|
|
438
|
-
/* @__PURE__ */ (0,
|
|
439
|
-
/* @__PURE__ */ (0,
|
|
1050
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M7 10v12" }),
|
|
1051
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" })
|
|
440
1052
|
]
|
|
441
1053
|
}
|
|
442
1054
|
)
|
|
443
1055
|
}
|
|
444
1056
|
),
|
|
445
|
-
/* @__PURE__ */ (0,
|
|
1057
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
446
1058
|
"button",
|
|
447
1059
|
{
|
|
448
1060
|
onClick: () => handleRate("negative"),
|
|
449
1061
|
disabled: rated !== null,
|
|
450
1062
|
style: buttonStyle(rated === "negative"),
|
|
451
1063
|
title: t("not_helpful"),
|
|
452
|
-
|
|
1064
|
+
"aria-label": t("not_helpful"),
|
|
1065
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
453
1066
|
"svg",
|
|
454
1067
|
{
|
|
455
1068
|
width: "14",
|
|
@@ -461,8 +1074,8 @@ function MessageRatingButtons({
|
|
|
461
1074
|
strokeLinecap: "round",
|
|
462
1075
|
strokeLinejoin: "round",
|
|
463
1076
|
children: [
|
|
464
|
-
/* @__PURE__ */ (0,
|
|
465
|
-
/* @__PURE__ */ (0,
|
|
1077
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M17 14V2" }),
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" })
|
|
466
1079
|
]
|
|
467
1080
|
}
|
|
468
1081
|
)
|
|
@@ -476,12 +1089,14 @@ function AnimatedMessage({
|
|
|
476
1089
|
animate,
|
|
477
1090
|
reduced
|
|
478
1091
|
}) {
|
|
479
|
-
const [visible, setVisible] = (0,
|
|
480
|
-
(0,
|
|
481
|
-
if (animate
|
|
482
|
-
|
|
483
|
-
return
|
|
1092
|
+
const [visible, setVisible] = (0, import_react7.useState)(!animate);
|
|
1093
|
+
(0, import_react7.useEffect)(() => {
|
|
1094
|
+
if (!animate || reduced) {
|
|
1095
|
+
setVisible(true);
|
|
1096
|
+
return;
|
|
484
1097
|
}
|
|
1098
|
+
const id = requestAnimationFrame(() => setVisible(true));
|
|
1099
|
+
return () => cancelAnimationFrame(id);
|
|
485
1100
|
}, [animate, reduced]);
|
|
486
1101
|
const style = {
|
|
487
1102
|
alignSelf: isUser ? "flex-end" : "flex-start",
|
|
@@ -490,16 +1105,123 @@ function AnimatedMessage({
|
|
|
490
1105
|
transform: visible ? "translateX(0)" : `translateX(${isUser ? "12px" : "-12px"})`,
|
|
491
1106
|
transition: animate && !reduced ? "opacity 0.2s ease, transform 0.2s ease" : "none"
|
|
492
1107
|
};
|
|
493
|
-
return /* @__PURE__ */ (0,
|
|
1108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style, children });
|
|
1109
|
+
}
|
|
1110
|
+
function ChipRow({
|
|
1111
|
+
options,
|
|
1112
|
+
onSelect,
|
|
1113
|
+
primaryColor,
|
|
1114
|
+
reduced
|
|
1115
|
+
}) {
|
|
1116
|
+
const chip = {
|
|
1117
|
+
background: "none",
|
|
1118
|
+
border: "1px solid #e0e0e0",
|
|
1119
|
+
borderRadius: 20,
|
|
1120
|
+
padding: "6px 12px",
|
|
1121
|
+
fontSize: 13,
|
|
1122
|
+
color: "#333",
|
|
1123
|
+
cursor: "pointer",
|
|
1124
|
+
textAlign: "left",
|
|
1125
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
1126
|
+
transition: reduced ? "none" : "border-color 0.15s, background 0.15s"
|
|
1127
|
+
};
|
|
1128
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1129
|
+
"div",
|
|
1130
|
+
{
|
|
1131
|
+
style: {
|
|
1132
|
+
display: "flex",
|
|
1133
|
+
flexWrap: "wrap",
|
|
1134
|
+
gap: 6,
|
|
1135
|
+
marginTop: 6
|
|
1136
|
+
},
|
|
1137
|
+
children: options.map((text) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1138
|
+
"button",
|
|
1139
|
+
{
|
|
1140
|
+
style: chip,
|
|
1141
|
+
onClick: () => onSelect(text),
|
|
1142
|
+
onMouseEnter: (e) => {
|
|
1143
|
+
e.currentTarget.style.borderColor = primaryColor;
|
|
1144
|
+
e.currentTarget.style.background = `${primaryColor}08`;
|
|
1145
|
+
},
|
|
1146
|
+
onMouseLeave: (e) => {
|
|
1147
|
+
e.currentTarget.style.borderColor = "#e0e0e0";
|
|
1148
|
+
e.currentTarget.style.background = "none";
|
|
1149
|
+
},
|
|
1150
|
+
children: text
|
|
1151
|
+
},
|
|
1152
|
+
text
|
|
1153
|
+
))
|
|
1154
|
+
}
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
function BlockRenderer({
|
|
1158
|
+
block,
|
|
1159
|
+
onSend,
|
|
1160
|
+
onApproveAction,
|
|
1161
|
+
onCancelAction,
|
|
1162
|
+
primaryColor,
|
|
1163
|
+
reduced,
|
|
1164
|
+
t
|
|
1165
|
+
}) {
|
|
1166
|
+
switch (block.type) {
|
|
1167
|
+
case "quick_replies":
|
|
1168
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1169
|
+
ChipRow,
|
|
1170
|
+
{
|
|
1171
|
+
options: block.options,
|
|
1172
|
+
onSelect: onSend,
|
|
1173
|
+
primaryColor,
|
|
1174
|
+
reduced
|
|
1175
|
+
}
|
|
1176
|
+
);
|
|
1177
|
+
case "action_confirmation":
|
|
1178
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1179
|
+
ActionConfirmationCard,
|
|
1180
|
+
{
|
|
1181
|
+
block,
|
|
1182
|
+
primaryColor,
|
|
1183
|
+
t,
|
|
1184
|
+
onApprove: onApproveAction,
|
|
1185
|
+
onCancel: onCancelAction
|
|
1186
|
+
}
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function StreamingCursor({ reduced }) {
|
|
1191
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1192
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: `@keyframes ch-caret-blink {
|
|
1193
|
+
0%, 50% { opacity: 1; }
|
|
1194
|
+
50.01%, 100% { opacity: 0; }
|
|
1195
|
+
}` }),
|
|
1196
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1197
|
+
"span",
|
|
1198
|
+
{
|
|
1199
|
+
"aria-hidden": "true",
|
|
1200
|
+
style: {
|
|
1201
|
+
display: "inline-block",
|
|
1202
|
+
width: 2,
|
|
1203
|
+
height: "1em",
|
|
1204
|
+
marginLeft: 2,
|
|
1205
|
+
verticalAlign: "text-bottom",
|
|
1206
|
+
background: "#777",
|
|
1207
|
+
animation: reduced ? "none" : "ch-caret-blink 1s step-start infinite"
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
)
|
|
1211
|
+
] });
|
|
494
1212
|
}
|
|
495
1213
|
function Message({
|
|
496
1214
|
message,
|
|
497
1215
|
config,
|
|
498
1216
|
onRate,
|
|
1217
|
+
onSend,
|
|
1218
|
+
onApproveAction,
|
|
1219
|
+
onCancelAction,
|
|
499
1220
|
hasConversation,
|
|
500
1221
|
t,
|
|
501
1222
|
animate,
|
|
502
|
-
reduced
|
|
1223
|
+
reduced,
|
|
1224
|
+
showSuggestions
|
|
503
1225
|
}) {
|
|
504
1226
|
const isUser = message.role === "user";
|
|
505
1227
|
const bubbleStyle = {
|
|
@@ -518,9 +1240,39 @@ function Message({
|
|
|
518
1240
|
borderBottomLeftRadius: 4
|
|
519
1241
|
}
|
|
520
1242
|
};
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
1243
|
+
const linkColor = isUser ? "#ffffff" : config.primaryColor;
|
|
1244
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(AnimatedMessage, { isUser, animate, reduced, children: [
|
|
1245
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: bubbleStyle, children: isUser ? message.content : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1246
|
+
renderMarkdown(message.content, {
|
|
1247
|
+
sources: message.sources,
|
|
1248
|
+
linkColor
|
|
1249
|
+
}),
|
|
1250
|
+
message.streaming && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StreamingCursor, { reduced })
|
|
1251
|
+
] }) }),
|
|
1252
|
+
isUser && message.status && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageStatusPill, { status: message.status, t }),
|
|
1253
|
+
!isUser && message.blocks?.map((block, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1254
|
+
BlockRenderer,
|
|
1255
|
+
{
|
|
1256
|
+
block,
|
|
1257
|
+
onSend,
|
|
1258
|
+
onApproveAction,
|
|
1259
|
+
onCancelAction,
|
|
1260
|
+
primaryColor: config.primaryColor,
|
|
1261
|
+
reduced,
|
|
1262
|
+
t
|
|
1263
|
+
},
|
|
1264
|
+
i
|
|
1265
|
+
)),
|
|
1266
|
+
!isUser && showSuggestions && message.suggestions?.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1267
|
+
ChipRow,
|
|
1268
|
+
{
|
|
1269
|
+
options: message.suggestions,
|
|
1270
|
+
onSelect: onSend,
|
|
1271
|
+
primaryColor: config.primaryColor,
|
|
1272
|
+
reduced
|
|
1273
|
+
}
|
|
1274
|
+
) : null,
|
|
1275
|
+
message.role === "bot" && message.id && hasConversation && !message.streaming && !message.blocks?.some((b) => b.type === "action_confirmation") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
524
1276
|
MessageRatingButtons,
|
|
525
1277
|
{
|
|
526
1278
|
messageId: message.id,
|
|
@@ -540,12 +1292,12 @@ function TypingDots({ reduced }) {
|
|
|
540
1292
|
background: "#999",
|
|
541
1293
|
animation: reduced ? "none" : `ch-dot-pulse 1.2s ease-in-out ${delay}s infinite`
|
|
542
1294
|
});
|
|
543
|
-
return /* @__PURE__ */ (0,
|
|
544
|
-
/* @__PURE__ */ (0,
|
|
1295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: `@keyframes ch-dot-pulse {
|
|
545
1297
|
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
|
546
1298
|
40% { opacity: 1; transform: scale(1); }
|
|
547
1299
|
}` }),
|
|
548
|
-
/* @__PURE__ */ (0,
|
|
1300
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
549
1301
|
"div",
|
|
550
1302
|
{
|
|
551
1303
|
style: {
|
|
@@ -559,21 +1311,32 @@ function TypingDots({ reduced }) {
|
|
|
559
1311
|
alignItems: "center"
|
|
560
1312
|
},
|
|
561
1313
|
children: [
|
|
562
|
-
/* @__PURE__ */ (0,
|
|
563
|
-
/* @__PURE__ */ (0,
|
|
564
|
-
/* @__PURE__ */ (0,
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dotStyle(0) }),
|
|
1315
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dotStyle(0.2) }),
|
|
1316
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dotStyle(0.4) })
|
|
565
1317
|
]
|
|
566
1318
|
}
|
|
567
1319
|
)
|
|
568
1320
|
] });
|
|
569
1321
|
}
|
|
570
1322
|
function ChatMessages() {
|
|
571
|
-
const {
|
|
1323
|
+
const {
|
|
1324
|
+
messages,
|
|
1325
|
+
isLoading,
|
|
1326
|
+
error,
|
|
1327
|
+
config,
|
|
1328
|
+
conversationId,
|
|
1329
|
+
rateMessage,
|
|
1330
|
+
sendMessage,
|
|
1331
|
+
approveAction,
|
|
1332
|
+
cancelAction,
|
|
1333
|
+
t
|
|
1334
|
+
} = useChat();
|
|
572
1335
|
const reduced = useReducedMotion();
|
|
573
|
-
const messagesEndRef = (0,
|
|
574
|
-
const isFirstRender = (0,
|
|
575
|
-
const prevMessageCount = (0,
|
|
576
|
-
(0,
|
|
1336
|
+
const messagesEndRef = (0, import_react7.useRef)(null);
|
|
1337
|
+
const isFirstRender = (0, import_react7.useRef)(true);
|
|
1338
|
+
const prevMessageCount = (0, import_react7.useRef)(0);
|
|
1339
|
+
(0, import_react7.useEffect)(() => {
|
|
577
1340
|
if (messagesEndRef.current) {
|
|
578
1341
|
messagesEndRef.current.scrollIntoView({
|
|
579
1342
|
behavior: isFirstRender.current || reduced ? "auto" : "smooth"
|
|
@@ -582,9 +1345,16 @@ function ChatMessages() {
|
|
|
582
1345
|
}
|
|
583
1346
|
}, [messages, isLoading, reduced]);
|
|
584
1347
|
const newStartIndex = isFirstRender.current ? messages.length : prevMessageCount.current;
|
|
585
|
-
(0,
|
|
1348
|
+
(0, import_react7.useEffect)(() => {
|
|
586
1349
|
prevMessageCount.current = messages.length;
|
|
587
1350
|
}, [messages.length]);
|
|
1351
|
+
let lastBotIndex = -1;
|
|
1352
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1353
|
+
if (messages[i].role === "bot") {
|
|
1354
|
+
lastBotIndex = i;
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
588
1358
|
const containerStyle = {
|
|
589
1359
|
flex: 1,
|
|
590
1360
|
overflowY: "auto",
|
|
@@ -593,24 +1363,31 @@ function ChatMessages() {
|
|
|
593
1363
|
flexDirection: "column",
|
|
594
1364
|
gap: 12
|
|
595
1365
|
};
|
|
596
|
-
|
|
597
|
-
|
|
1366
|
+
const lastMsg = messages[messages.length - 1];
|
|
1367
|
+
const waitingForFirstToken = isLoading && (lastMsg?.role !== "bot" || lastMsg.streaming !== true);
|
|
1368
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: containerStyle, children: [
|
|
1369
|
+
messages.map((msg, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
598
1370
|
Message,
|
|
599
1371
|
{
|
|
600
1372
|
message: msg,
|
|
601
1373
|
config,
|
|
602
1374
|
onRate: rateMessage,
|
|
1375
|
+
onSend: sendMessage,
|
|
1376
|
+
onApproveAction: approveAction,
|
|
1377
|
+
onCancelAction: cancelAction,
|
|
603
1378
|
hasConversation: conversationId !== null,
|
|
604
1379
|
t,
|
|
605
1380
|
animate: i >= newStartIndex,
|
|
606
|
-
reduced
|
|
1381
|
+
reduced,
|
|
1382
|
+
showSuggestions: i === lastBotIndex && !isLoading && msg.streaming !== true
|
|
607
1383
|
},
|
|
608
1384
|
i
|
|
609
1385
|
)),
|
|
610
|
-
|
|
611
|
-
error && /* @__PURE__ */ (0,
|
|
1386
|
+
waitingForFirstToken && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TypingDots, { reduced }),
|
|
1387
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
612
1388
|
"div",
|
|
613
1389
|
{
|
|
1390
|
+
role: "alert",
|
|
614
1391
|
style: {
|
|
615
1392
|
alignSelf: "flex-start",
|
|
616
1393
|
padding: "10px 14px",
|
|
@@ -623,12 +1400,12 @@ function ChatMessages() {
|
|
|
623
1400
|
children: error
|
|
624
1401
|
}
|
|
625
1402
|
),
|
|
626
|
-
/* @__PURE__ */ (0,
|
|
1403
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: messagesEndRef })
|
|
627
1404
|
] });
|
|
628
1405
|
}
|
|
629
1406
|
|
|
630
1407
|
// src/components/chat-suggestions.tsx
|
|
631
|
-
var
|
|
1408
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
632
1409
|
function ChatSuggestions() {
|
|
633
1410
|
const { messages, isLoading, config, sendMessage } = useChat();
|
|
634
1411
|
const reduced = useReducedMotion();
|
|
@@ -655,7 +1432,7 @@ function ChatSuggestions() {
|
|
|
655
1432
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
656
1433
|
transition: reduced ? "none" : "border-color 0.15s, background 0.15s"
|
|
657
1434
|
};
|
|
658
|
-
return /* @__PURE__ */ (0,
|
|
1435
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: containerStyle, children: config.suggestedMessages.map((text) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
659
1436
|
"button",
|
|
660
1437
|
{
|
|
661
1438
|
style: chipStyle,
|
|
@@ -675,15 +1452,75 @@ function ChatSuggestions() {
|
|
|
675
1452
|
}
|
|
676
1453
|
|
|
677
1454
|
// src/components/chat-input.tsx
|
|
678
|
-
var
|
|
679
|
-
var
|
|
1455
|
+
var import_react8 = require("react");
|
|
1456
|
+
var import_js2 = require("@customerhero/js");
|
|
1457
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1458
|
+
var MAX_ATTACHMENTS = 3;
|
|
680
1459
|
function ChatInput() {
|
|
681
|
-
const { sendMessage, isLoading, config, t } = useChat();
|
|
1460
|
+
const { sendMessage, uploadAttachment, isLoading, config, t } = useChat();
|
|
682
1461
|
const reduced = useReducedMotion();
|
|
683
|
-
const [value, setValue] = (0,
|
|
1462
|
+
const [value, setValue] = (0, import_react8.useState)("");
|
|
1463
|
+
const [attachments, setAttachments] = (0, import_react8.useState)([]);
|
|
1464
|
+
const [captureSupported, setCaptureSupported] = (0, import_react8.useState)(false);
|
|
1465
|
+
(0, import_react8.useEffect)(() => {
|
|
1466
|
+
setCaptureSupported((0, import_js2.canCaptureScreenshot)());
|
|
1467
|
+
}, []);
|
|
1468
|
+
(0, import_react8.useEffect)(() => {
|
|
1469
|
+
return () => {
|
|
1470
|
+
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1471
|
+
};
|
|
1472
|
+
}, []);
|
|
1473
|
+
const updateAttachment = (id, patch) => {
|
|
1474
|
+
setAttachments(
|
|
1475
|
+
(current) => current.map(
|
|
1476
|
+
(a) => a.id === id ? { ...a, ...patch } : a
|
|
1477
|
+
)
|
|
1478
|
+
);
|
|
1479
|
+
};
|
|
1480
|
+
const startUpload = async (blob) => {
|
|
1481
|
+
if (attachments.length >= MAX_ATTACHMENTS) return;
|
|
1482
|
+
const id = `att_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1483
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
1484
|
+
setAttachments((current) => [
|
|
1485
|
+
...current,
|
|
1486
|
+
{ id, status: "uploading", previewUrl, blob }
|
|
1487
|
+
]);
|
|
1488
|
+
try {
|
|
1489
|
+
const { attachmentToken } = await uploadAttachment(blob);
|
|
1490
|
+
updateAttachment(id, {
|
|
1491
|
+
status: "ready",
|
|
1492
|
+
token: attachmentToken
|
|
1493
|
+
});
|
|
1494
|
+
} catch {
|
|
1495
|
+
updateAttachment(id, { status: "error" });
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
const handleCapture = async () => {
|
|
1499
|
+
try {
|
|
1500
|
+
const blob = await (0, import_js2.captureScreenshot)();
|
|
1501
|
+
await startUpload(blob);
|
|
1502
|
+
} catch (e) {
|
|
1503
|
+
if (e instanceof import_js2.ScreenshotCancelled) return;
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
const handleRemove = (id) => {
|
|
1507
|
+
setAttachments((current) => {
|
|
1508
|
+
const target = current.find((a) => a.id === id);
|
|
1509
|
+
if (target) URL.revokeObjectURL(target.previewUrl);
|
|
1510
|
+
return current.filter((a) => a.id !== id);
|
|
1511
|
+
});
|
|
1512
|
+
};
|
|
1513
|
+
const readyTokens = attachments.filter(
|
|
1514
|
+
(a) => a.status === "ready"
|
|
1515
|
+
).map((a) => a.token);
|
|
684
1516
|
const handleSend = () => {
|
|
685
1517
|
if (!value.trim() || isLoading) return;
|
|
686
|
-
sendMessage(
|
|
1518
|
+
sendMessage(
|
|
1519
|
+
value,
|
|
1520
|
+
readyTokens.length > 0 ? { attachmentTokens: readyTokens } : void 0
|
|
1521
|
+
);
|
|
1522
|
+
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1523
|
+
setAttachments([]);
|
|
687
1524
|
setValue("");
|
|
688
1525
|
};
|
|
689
1526
|
const handleKeyDown = (e) => {
|
|
@@ -695,6 +1532,11 @@ function ChatInput() {
|
|
|
695
1532
|
const containerStyle = {
|
|
696
1533
|
padding: "12px 16px",
|
|
697
1534
|
borderTop: "1px solid #eee",
|
|
1535
|
+
display: "flex",
|
|
1536
|
+
flexDirection: "column",
|
|
1537
|
+
gap: 8
|
|
1538
|
+
};
|
|
1539
|
+
const rowStyle = {
|
|
698
1540
|
display: "flex",
|
|
699
1541
|
alignItems: "center",
|
|
700
1542
|
gap: 8
|
|
@@ -710,7 +1552,7 @@ function ChatInput() {
|
|
|
710
1552
|
color: config.textColor,
|
|
711
1553
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
712
1554
|
};
|
|
713
|
-
const
|
|
1555
|
+
const sendButtonStyle = {
|
|
714
1556
|
width: 36,
|
|
715
1557
|
height: 36,
|
|
716
1558
|
borderRadius: "50%",
|
|
@@ -726,57 +1568,210 @@ function ChatInput() {
|
|
|
726
1568
|
flexShrink: 0,
|
|
727
1569
|
transform: "scale(1)"
|
|
728
1570
|
};
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1571
|
+
const iconButtonStyle = (disabled) => ({
|
|
1572
|
+
width: 36,
|
|
1573
|
+
height: 36,
|
|
1574
|
+
borderRadius: "50%",
|
|
1575
|
+
background: "transparent",
|
|
1576
|
+
border: "none",
|
|
1577
|
+
color: disabled ? "#ccc" : "#666",
|
|
1578
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1579
|
+
display: "flex",
|
|
1580
|
+
alignItems: "center",
|
|
1581
|
+
justifyContent: "center",
|
|
1582
|
+
flexShrink: 0,
|
|
1583
|
+
padding: 0
|
|
1584
|
+
});
|
|
1585
|
+
const captureDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
|
|
1586
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: containerStyle, children: [
|
|
1587
|
+
attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1588
|
+
"div",
|
|
732
1589
|
{
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1590
|
+
style: { display: "flex", gap: 8, flexWrap: "wrap" },
|
|
1591
|
+
"aria-label": "Attachments",
|
|
1592
|
+
children: attachments.map((a) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1593
|
+
Thumbnail,
|
|
1594
|
+
{
|
|
1595
|
+
attachment: a,
|
|
1596
|
+
onRemove: () => handleRemove(a.id),
|
|
1597
|
+
t
|
|
1598
|
+
},
|
|
1599
|
+
a.id
|
|
1600
|
+
))
|
|
740
1601
|
}
|
|
741
1602
|
),
|
|
742
|
-
/* @__PURE__ */ (0,
|
|
1603
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
|
|
1604
|
+
captureSupported && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1605
|
+
"button",
|
|
1606
|
+
{
|
|
1607
|
+
type: "button",
|
|
1608
|
+
onClick: handleCapture,
|
|
1609
|
+
disabled: captureDisabled,
|
|
1610
|
+
style: iconButtonStyle(captureDisabled),
|
|
1611
|
+
"aria-label": t("screenshot_capture"),
|
|
1612
|
+
title: t("screenshot_capture"),
|
|
1613
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CameraIcon, {})
|
|
1614
|
+
}
|
|
1615
|
+
),
|
|
1616
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1617
|
+
"input",
|
|
1618
|
+
{
|
|
1619
|
+
type: "text",
|
|
1620
|
+
value,
|
|
1621
|
+
onChange: (e) => setValue(e.target.value),
|
|
1622
|
+
onKeyDown: handleKeyDown,
|
|
1623
|
+
placeholder: config.placeholderText,
|
|
1624
|
+
style: inputStyle,
|
|
1625
|
+
disabled: isLoading
|
|
1626
|
+
}
|
|
1627
|
+
),
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1629
|
+
"button",
|
|
1630
|
+
{
|
|
1631
|
+
onClick: handleSend,
|
|
1632
|
+
disabled: isLoading || !value.trim(),
|
|
1633
|
+
style: sendButtonStyle,
|
|
1634
|
+
"aria-label": t("send_message"),
|
|
1635
|
+
onMouseEnter: (e) => {
|
|
1636
|
+
if (!reduced && !isLoading)
|
|
1637
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
1638
|
+
},
|
|
1639
|
+
onMouseLeave: (e) => {
|
|
1640
|
+
if (!reduced) e.currentTarget.style.transform = "scale(1)";
|
|
1641
|
+
},
|
|
1642
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1643
|
+
"svg",
|
|
1644
|
+
{
|
|
1645
|
+
width: "16",
|
|
1646
|
+
height: "16",
|
|
1647
|
+
viewBox: "0 0 24 24",
|
|
1648
|
+
fill: "none",
|
|
1649
|
+
stroke: "currentColor",
|
|
1650
|
+
strokeWidth: "2",
|
|
1651
|
+
strokeLinecap: "round",
|
|
1652
|
+
strokeLinejoin: "round",
|
|
1653
|
+
children: [
|
|
1654
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
1655
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
1656
|
+
]
|
|
1657
|
+
}
|
|
1658
|
+
)
|
|
1659
|
+
}
|
|
1660
|
+
)
|
|
1661
|
+
] })
|
|
1662
|
+
] });
|
|
1663
|
+
}
|
|
1664
|
+
function Thumbnail({
|
|
1665
|
+
attachment,
|
|
1666
|
+
onRemove,
|
|
1667
|
+
t
|
|
1668
|
+
}) {
|
|
1669
|
+
const { status, previewUrl } = attachment;
|
|
1670
|
+
const wrap = {
|
|
1671
|
+
position: "relative",
|
|
1672
|
+
width: 56,
|
|
1673
|
+
height: 56,
|
|
1674
|
+
borderRadius: 8,
|
|
1675
|
+
overflow: "hidden",
|
|
1676
|
+
border: status === "error" ? "2px solid #b91c1c" : "1px solid #e0e0e0",
|
|
1677
|
+
background: "#f5f5f5"
|
|
1678
|
+
};
|
|
1679
|
+
const img = {
|
|
1680
|
+
width: "100%",
|
|
1681
|
+
height: "100%",
|
|
1682
|
+
objectFit: "cover"
|
|
1683
|
+
};
|
|
1684
|
+
const removeBtn = {
|
|
1685
|
+
position: "absolute",
|
|
1686
|
+
top: 2,
|
|
1687
|
+
right: 2,
|
|
1688
|
+
width: 18,
|
|
1689
|
+
height: 18,
|
|
1690
|
+
borderRadius: "50%",
|
|
1691
|
+
border: "none",
|
|
1692
|
+
background: "rgba(0,0,0,0.6)",
|
|
1693
|
+
color: "white",
|
|
1694
|
+
fontSize: 11,
|
|
1695
|
+
lineHeight: "18px",
|
|
1696
|
+
padding: 0,
|
|
1697
|
+
cursor: "pointer",
|
|
1698
|
+
textAlign: "center"
|
|
1699
|
+
};
|
|
1700
|
+
const overlay = {
|
|
1701
|
+
position: "absolute",
|
|
1702
|
+
inset: 0,
|
|
1703
|
+
background: "rgba(255,255,255,0.7)",
|
|
1704
|
+
display: "flex",
|
|
1705
|
+
alignItems: "center",
|
|
1706
|
+
justifyContent: "center"
|
|
1707
|
+
};
|
|
1708
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: wrap, children: [
|
|
1709
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: previewUrl, alt: "", style: img }),
|
|
1710
|
+
status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: overlay, "aria-label": "Uploading", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Spinner2, {}) }),
|
|
1711
|
+
status === "error" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1712
|
+
"div",
|
|
1713
|
+
{
|
|
1714
|
+
style: { ...overlay, background: "rgba(255,255,255,0.85)" },
|
|
1715
|
+
"aria-label": t("status_failed"),
|
|
1716
|
+
title: t("status_failed"),
|
|
1717
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { color: "#b91c1c", fontSize: 11, fontWeight: 600 }, children: "!" })
|
|
1718
|
+
}
|
|
1719
|
+
),
|
|
1720
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
743
1721
|
"button",
|
|
744
1722
|
{
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
"aria-label": t("
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1723
|
+
type: "button",
|
|
1724
|
+
style: removeBtn,
|
|
1725
|
+
onClick: onRemove,
|
|
1726
|
+
"aria-label": t("attachment_remove"),
|
|
1727
|
+
children: "\xD7"
|
|
1728
|
+
}
|
|
1729
|
+
)
|
|
1730
|
+
] });
|
|
1731
|
+
}
|
|
1732
|
+
function CameraIcon() {
|
|
1733
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1734
|
+
"svg",
|
|
1735
|
+
{
|
|
1736
|
+
width: "20",
|
|
1737
|
+
height: "20",
|
|
1738
|
+
viewBox: "0 0 24 24",
|
|
1739
|
+
fill: "none",
|
|
1740
|
+
stroke: "currentColor",
|
|
1741
|
+
strokeWidth: "2",
|
|
1742
|
+
strokeLinecap: "round",
|
|
1743
|
+
strokeLinejoin: "round",
|
|
1744
|
+
"aria-hidden": "true",
|
|
1745
|
+
children: [
|
|
1746
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("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" }),
|
|
1747
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "13", r: "4" })
|
|
1748
|
+
]
|
|
1749
|
+
}
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
function Spinner2() {
|
|
1753
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
1754
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `@keyframes ch-att-spin { to { transform: rotate(360deg); } }` }),
|
|
1755
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1756
|
+
"span",
|
|
1757
|
+
{
|
|
1758
|
+
"aria-hidden": "true",
|
|
1759
|
+
style: {
|
|
1760
|
+
width: 16,
|
|
1761
|
+
height: 16,
|
|
1762
|
+
borderRadius: "50%",
|
|
1763
|
+
border: "2px solid #888",
|
|
1764
|
+
borderTopColor: "transparent",
|
|
1765
|
+
animation: "ch-att-spin 0.8s linear infinite",
|
|
1766
|
+
display: "inline-block"
|
|
1767
|
+
}
|
|
773
1768
|
}
|
|
774
1769
|
)
|
|
775
1770
|
] });
|
|
776
1771
|
}
|
|
777
1772
|
|
|
778
1773
|
// src/components/chat-window.tsx
|
|
779
|
-
var
|
|
1774
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
780
1775
|
function ConfigError({ message, title }) {
|
|
781
1776
|
const errorStyle = {
|
|
782
1777
|
flex: 1,
|
|
@@ -788,8 +1783,8 @@ function ConfigError({ message, title }) {
|
|
|
788
1783
|
textAlign: "center",
|
|
789
1784
|
gap: 8
|
|
790
1785
|
};
|
|
791
|
-
return /* @__PURE__ */ (0,
|
|
792
|
-
/* @__PURE__ */ (0,
|
|
1786
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: errorStyle, children: [
|
|
1787
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
793
1788
|
"svg",
|
|
794
1789
|
{
|
|
795
1790
|
width: "32",
|
|
@@ -801,22 +1796,22 @@ function ConfigError({ message, title }) {
|
|
|
801
1796
|
strokeLinecap: "round",
|
|
802
1797
|
strokeLinejoin: "round",
|
|
803
1798
|
children: [
|
|
804
|
-
/* @__PURE__ */ (0,
|
|
805
|
-
/* @__PURE__ */ (0,
|
|
806
|
-
/* @__PURE__ */ (0,
|
|
1799
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1800
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
1801
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
807
1802
|
]
|
|
808
1803
|
}
|
|
809
1804
|
),
|
|
810
|
-
/* @__PURE__ */ (0,
|
|
811
|
-
/* @__PURE__ */ (0,
|
|
1805
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 14, fontWeight: 500, color: "#333", margin: 0 }, children: title }),
|
|
1806
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
|
|
812
1807
|
] });
|
|
813
1808
|
}
|
|
814
1809
|
function ChatWindow() {
|
|
815
|
-
const { isOpen, config, configError, t } = useChat();
|
|
1810
|
+
const { isOpen, config, configError, t, isRtl } = useChat();
|
|
816
1811
|
const reduced = useReducedMotion();
|
|
817
|
-
const [visible, setVisible] = (0,
|
|
818
|
-
const [shouldRender, setShouldRender] = (0,
|
|
819
|
-
(0,
|
|
1812
|
+
const [visible, setVisible] = (0, import_react9.useState)(false);
|
|
1813
|
+
const [shouldRender, setShouldRender] = (0, import_react9.useState)(false);
|
|
1814
|
+
(0, import_react9.useEffect)(() => {
|
|
820
1815
|
if (isOpen) {
|
|
821
1816
|
setShouldRender(true);
|
|
822
1817
|
requestAnimationFrame(() => {
|
|
@@ -833,10 +1828,11 @@ function ChatWindow() {
|
|
|
833
1828
|
}
|
|
834
1829
|
}, [isOpen, reduced]);
|
|
835
1830
|
if (!shouldRender) return null;
|
|
1831
|
+
const effectivePosition = isRtl ? config.position === "bottom-right" ? "bottom-left" : "bottom-right" : config.position;
|
|
836
1832
|
const style = {
|
|
837
1833
|
position: "fixed",
|
|
838
1834
|
bottom: 90,
|
|
839
|
-
[
|
|
1835
|
+
[effectivePosition === "bottom-left" ? "left" : "right"]: 20,
|
|
840
1836
|
width: 380,
|
|
841
1837
|
maxWidth: "calc(100vw - 40px)",
|
|
842
1838
|
height: 520,
|
|
@@ -864,17 +1860,17 @@ function ChatWindow() {
|
|
|
864
1860
|
textDecoration: "underline",
|
|
865
1861
|
textUnderlineOffset: 2
|
|
866
1862
|
};
|
|
867
|
-
return /* @__PURE__ */ (0,
|
|
868
|
-
/* @__PURE__ */ (0,
|
|
869
|
-
configError ? /* @__PURE__ */ (0,
|
|
870
|
-
/* @__PURE__ */ (0,
|
|
871
|
-
/* @__PURE__ */ (0,
|
|
872
|
-
/* @__PURE__ */ (0,
|
|
1863
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style, dir: isRtl ? "rtl" : "ltr", children: [
|
|
1864
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatHeader, {}),
|
|
1865
|
+
configError ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ConfigError, { title: t("unable_to_load"), message: configError }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
1866
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatMessages, {}),
|
|
1867
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatSuggestions, {}),
|
|
1868
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatInput, {})
|
|
873
1869
|
] }),
|
|
874
|
-
/* @__PURE__ */ (0,
|
|
1870
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: poweredStyle, children: [
|
|
875
1871
|
t("powered_by"),
|
|
876
1872
|
" ",
|
|
877
|
-
/* @__PURE__ */ (0,
|
|
1873
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
878
1874
|
"a",
|
|
879
1875
|
{
|
|
880
1876
|
href: "https://customerhero.app",
|
|
@@ -889,11 +1885,11 @@ function ChatWindow() {
|
|
|
889
1885
|
}
|
|
890
1886
|
|
|
891
1887
|
// src/components/chat-widget.tsx
|
|
892
|
-
var
|
|
1888
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
893
1889
|
function ChatWidgetInner({ identity }) {
|
|
894
1890
|
const client = useCustomerHeroClient();
|
|
895
|
-
const prevIdentityRef = (0,
|
|
896
|
-
(0,
|
|
1891
|
+
const prevIdentityRef = (0, import_react10.useRef)(void 0);
|
|
1892
|
+
(0, import_react10.useEffect)(() => {
|
|
897
1893
|
const key = identity ? JSON.stringify(identity) : void 0;
|
|
898
1894
|
if (key !== prevIdentityRef.current) {
|
|
899
1895
|
prevIdentityRef.current = key;
|
|
@@ -902,15 +1898,17 @@ function ChatWidgetInner({ identity }) {
|
|
|
902
1898
|
}
|
|
903
1899
|
}
|
|
904
1900
|
}, [identity, client]);
|
|
905
|
-
return /* @__PURE__ */ (0,
|
|
906
|
-
/* @__PURE__ */ (0,
|
|
907
|
-
/* @__PURE__ */ (0,
|
|
1901
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
1902
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatBubble, {}),
|
|
1903
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatWindow, {})
|
|
908
1904
|
] });
|
|
909
1905
|
}
|
|
910
1906
|
function ChatWidget({ identity, ...config }) {
|
|
911
|
-
return /* @__PURE__ */ (0,
|
|
1907
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatWidgetInner, { identity }) });
|
|
912
1908
|
}
|
|
913
1909
|
// Annotate the CommonJS export names for ESM import in node:
|
|
914
1910
|
0 && (module.exports = {
|
|
915
|
-
|
|
1911
|
+
ActionConfirmationCard,
|
|
1912
|
+
ChatWidget,
|
|
1913
|
+
useChat
|
|
916
1914
|
});
|