@customerhero/react 0.0.2 → 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.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,8 +1089,8 @@ function AnimatedMessage({
|
|
|
476
1089
|
animate,
|
|
477
1090
|
reduced
|
|
478
1091
|
}) {
|
|
479
|
-
const [visible, setVisible] = (0,
|
|
480
|
-
(0,
|
|
1092
|
+
const [visible, setVisible] = (0, import_react7.useState)(!animate);
|
|
1093
|
+
(0, import_react7.useEffect)(() => {
|
|
481
1094
|
if (animate && !reduced) {
|
|
482
1095
|
const id = requestAnimationFrame(() => setVisible(true));
|
|
483
1096
|
return () => cancelAnimationFrame(id);
|
|
@@ -490,16 +1103,123 @@ function AnimatedMessage({
|
|
|
490
1103
|
transform: visible ? "translateX(0)" : `translateX(${isUser ? "12px" : "-12px"})`,
|
|
491
1104
|
transition: animate && !reduced ? "opacity 0.2s ease, transform 0.2s ease" : "none"
|
|
492
1105
|
};
|
|
493
|
-
return /* @__PURE__ */ (0,
|
|
1106
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style, children });
|
|
1107
|
+
}
|
|
1108
|
+
function ChipRow({
|
|
1109
|
+
options,
|
|
1110
|
+
onSelect,
|
|
1111
|
+
primaryColor,
|
|
1112
|
+
reduced
|
|
1113
|
+
}) {
|
|
1114
|
+
const chip = {
|
|
1115
|
+
background: "none",
|
|
1116
|
+
border: "1px solid #e0e0e0",
|
|
1117
|
+
borderRadius: 20,
|
|
1118
|
+
padding: "6px 12px",
|
|
1119
|
+
fontSize: 13,
|
|
1120
|
+
color: "#333",
|
|
1121
|
+
cursor: "pointer",
|
|
1122
|
+
textAlign: "left",
|
|
1123
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
1124
|
+
transition: reduced ? "none" : "border-color 0.15s, background 0.15s"
|
|
1125
|
+
};
|
|
1126
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1127
|
+
"div",
|
|
1128
|
+
{
|
|
1129
|
+
style: {
|
|
1130
|
+
display: "flex",
|
|
1131
|
+
flexWrap: "wrap",
|
|
1132
|
+
gap: 6,
|
|
1133
|
+
marginTop: 6
|
|
1134
|
+
},
|
|
1135
|
+
children: options.map((text) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1136
|
+
"button",
|
|
1137
|
+
{
|
|
1138
|
+
style: chip,
|
|
1139
|
+
onClick: () => onSelect(text),
|
|
1140
|
+
onMouseEnter: (e) => {
|
|
1141
|
+
e.currentTarget.style.borderColor = primaryColor;
|
|
1142
|
+
e.currentTarget.style.background = `${primaryColor}08`;
|
|
1143
|
+
},
|
|
1144
|
+
onMouseLeave: (e) => {
|
|
1145
|
+
e.currentTarget.style.borderColor = "#e0e0e0";
|
|
1146
|
+
e.currentTarget.style.background = "none";
|
|
1147
|
+
},
|
|
1148
|
+
children: text
|
|
1149
|
+
},
|
|
1150
|
+
text
|
|
1151
|
+
))
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
function BlockRenderer({
|
|
1156
|
+
block,
|
|
1157
|
+
onSend,
|
|
1158
|
+
onApproveAction,
|
|
1159
|
+
onCancelAction,
|
|
1160
|
+
primaryColor,
|
|
1161
|
+
reduced,
|
|
1162
|
+
t
|
|
1163
|
+
}) {
|
|
1164
|
+
switch (block.type) {
|
|
1165
|
+
case "quick_replies":
|
|
1166
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1167
|
+
ChipRow,
|
|
1168
|
+
{
|
|
1169
|
+
options: block.options,
|
|
1170
|
+
onSelect: onSend,
|
|
1171
|
+
primaryColor,
|
|
1172
|
+
reduced
|
|
1173
|
+
}
|
|
1174
|
+
);
|
|
1175
|
+
case "action_confirmation":
|
|
1176
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1177
|
+
ActionConfirmationCard,
|
|
1178
|
+
{
|
|
1179
|
+
block,
|
|
1180
|
+
primaryColor,
|
|
1181
|
+
t,
|
|
1182
|
+
onApprove: onApproveAction,
|
|
1183
|
+
onCancel: onCancelAction
|
|
1184
|
+
}
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
function StreamingCursor({ reduced }) {
|
|
1189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1190
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: `@keyframes ch-caret-blink {
|
|
1191
|
+
0%, 50% { opacity: 1; }
|
|
1192
|
+
50.01%, 100% { opacity: 0; }
|
|
1193
|
+
}` }),
|
|
1194
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1195
|
+
"span",
|
|
1196
|
+
{
|
|
1197
|
+
"aria-hidden": "true",
|
|
1198
|
+
style: {
|
|
1199
|
+
display: "inline-block",
|
|
1200
|
+
width: 2,
|
|
1201
|
+
height: "1em",
|
|
1202
|
+
marginLeft: 2,
|
|
1203
|
+
verticalAlign: "text-bottom",
|
|
1204
|
+
background: "#777",
|
|
1205
|
+
animation: reduced ? "none" : "ch-caret-blink 1s step-start infinite"
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
)
|
|
1209
|
+
] });
|
|
494
1210
|
}
|
|
495
1211
|
function Message({
|
|
496
1212
|
message,
|
|
497
1213
|
config,
|
|
498
1214
|
onRate,
|
|
1215
|
+
onSend,
|
|
1216
|
+
onApproveAction,
|
|
1217
|
+
onCancelAction,
|
|
499
1218
|
hasConversation,
|
|
500
1219
|
t,
|
|
501
1220
|
animate,
|
|
502
|
-
reduced
|
|
1221
|
+
reduced,
|
|
1222
|
+
showSuggestions
|
|
503
1223
|
}) {
|
|
504
1224
|
const isUser = message.role === "user";
|
|
505
1225
|
const bubbleStyle = {
|
|
@@ -518,9 +1238,39 @@ function Message({
|
|
|
518
1238
|
borderBottomLeftRadius: 4
|
|
519
1239
|
}
|
|
520
1240
|
};
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
1241
|
+
const linkColor = isUser ? "#ffffff" : config.primaryColor;
|
|
1242
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(AnimatedMessage, { isUser, animate, reduced, children: [
|
|
1243
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: bubbleStyle, children: isUser ? message.content : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1244
|
+
renderMarkdown(message.content, {
|
|
1245
|
+
sources: message.sources,
|
|
1246
|
+
linkColor
|
|
1247
|
+
}),
|
|
1248
|
+
message.streaming && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StreamingCursor, { reduced })
|
|
1249
|
+
] }) }),
|
|
1250
|
+
isUser && message.status && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageStatusPill, { status: message.status, t }),
|
|
1251
|
+
!isUser && message.blocks?.map((block, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1252
|
+
BlockRenderer,
|
|
1253
|
+
{
|
|
1254
|
+
block,
|
|
1255
|
+
onSend,
|
|
1256
|
+
onApproveAction,
|
|
1257
|
+
onCancelAction,
|
|
1258
|
+
primaryColor: config.primaryColor,
|
|
1259
|
+
reduced,
|
|
1260
|
+
t
|
|
1261
|
+
},
|
|
1262
|
+
i
|
|
1263
|
+
)),
|
|
1264
|
+
!isUser && showSuggestions && message.suggestions?.length ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1265
|
+
ChipRow,
|
|
1266
|
+
{
|
|
1267
|
+
options: message.suggestions,
|
|
1268
|
+
onSelect: onSend,
|
|
1269
|
+
primaryColor: config.primaryColor,
|
|
1270
|
+
reduced
|
|
1271
|
+
}
|
|
1272
|
+
) : null,
|
|
1273
|
+
message.role === "bot" && message.id && hasConversation && !message.streaming && !message.blocks?.some((b) => b.type === "action_confirmation") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
524
1274
|
MessageRatingButtons,
|
|
525
1275
|
{
|
|
526
1276
|
messageId: message.id,
|
|
@@ -540,12 +1290,12 @@ function TypingDots({ reduced }) {
|
|
|
540
1290
|
background: "#999",
|
|
541
1291
|
animation: reduced ? "none" : `ch-dot-pulse 1.2s ease-in-out ${delay}s infinite`
|
|
542
1292
|
});
|
|
543
|
-
return /* @__PURE__ */ (0,
|
|
544
|
-
/* @__PURE__ */ (0,
|
|
1293
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1294
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("style", { children: `@keyframes ch-dot-pulse {
|
|
545
1295
|
0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
|
|
546
1296
|
40% { opacity: 1; transform: scale(1); }
|
|
547
1297
|
}` }),
|
|
548
|
-
/* @__PURE__ */ (0,
|
|
1298
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
549
1299
|
"div",
|
|
550
1300
|
{
|
|
551
1301
|
style: {
|
|
@@ -559,21 +1309,32 @@ function TypingDots({ reduced }) {
|
|
|
559
1309
|
alignItems: "center"
|
|
560
1310
|
},
|
|
561
1311
|
children: [
|
|
562
|
-
/* @__PURE__ */ (0,
|
|
563
|
-
/* @__PURE__ */ (0,
|
|
564
|
-
/* @__PURE__ */ (0,
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dotStyle(0) }),
|
|
1313
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dotStyle(0.2) }),
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: dotStyle(0.4) })
|
|
565
1315
|
]
|
|
566
1316
|
}
|
|
567
1317
|
)
|
|
568
1318
|
] });
|
|
569
1319
|
}
|
|
570
1320
|
function ChatMessages() {
|
|
571
|
-
const {
|
|
1321
|
+
const {
|
|
1322
|
+
messages,
|
|
1323
|
+
isLoading,
|
|
1324
|
+
error,
|
|
1325
|
+
config,
|
|
1326
|
+
conversationId,
|
|
1327
|
+
rateMessage,
|
|
1328
|
+
sendMessage,
|
|
1329
|
+
approveAction,
|
|
1330
|
+
cancelAction,
|
|
1331
|
+
t
|
|
1332
|
+
} = useChat();
|
|
572
1333
|
const reduced = useReducedMotion();
|
|
573
|
-
const messagesEndRef = (0,
|
|
574
|
-
const isFirstRender = (0,
|
|
575
|
-
const prevMessageCount = (0,
|
|
576
|
-
(0,
|
|
1334
|
+
const messagesEndRef = (0, import_react7.useRef)(null);
|
|
1335
|
+
const isFirstRender = (0, import_react7.useRef)(true);
|
|
1336
|
+
const prevMessageCount = (0, import_react7.useRef)(0);
|
|
1337
|
+
(0, import_react7.useEffect)(() => {
|
|
577
1338
|
if (messagesEndRef.current) {
|
|
578
1339
|
messagesEndRef.current.scrollIntoView({
|
|
579
1340
|
behavior: isFirstRender.current || reduced ? "auto" : "smooth"
|
|
@@ -582,9 +1343,16 @@ function ChatMessages() {
|
|
|
582
1343
|
}
|
|
583
1344
|
}, [messages, isLoading, reduced]);
|
|
584
1345
|
const newStartIndex = isFirstRender.current ? messages.length : prevMessageCount.current;
|
|
585
|
-
(0,
|
|
1346
|
+
(0, import_react7.useEffect)(() => {
|
|
586
1347
|
prevMessageCount.current = messages.length;
|
|
587
1348
|
}, [messages.length]);
|
|
1349
|
+
let lastBotIndex = -1;
|
|
1350
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1351
|
+
if (messages[i].role === "bot") {
|
|
1352
|
+
lastBotIndex = i;
|
|
1353
|
+
break;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
588
1356
|
const containerStyle = {
|
|
589
1357
|
flex: 1,
|
|
590
1358
|
overflowY: "auto",
|
|
@@ -593,24 +1361,31 @@ function ChatMessages() {
|
|
|
593
1361
|
flexDirection: "column",
|
|
594
1362
|
gap: 12
|
|
595
1363
|
};
|
|
596
|
-
|
|
597
|
-
|
|
1364
|
+
const lastMsg = messages[messages.length - 1];
|
|
1365
|
+
const waitingForFirstToken = isLoading && (lastMsg?.role !== "bot" || lastMsg.streaming !== true);
|
|
1366
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: containerStyle, children: [
|
|
1367
|
+
messages.map((msg, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
598
1368
|
Message,
|
|
599
1369
|
{
|
|
600
1370
|
message: msg,
|
|
601
1371
|
config,
|
|
602
1372
|
onRate: rateMessage,
|
|
1373
|
+
onSend: sendMessage,
|
|
1374
|
+
onApproveAction: approveAction,
|
|
1375
|
+
onCancelAction: cancelAction,
|
|
603
1376
|
hasConversation: conversationId !== null,
|
|
604
1377
|
t,
|
|
605
1378
|
animate: i >= newStartIndex,
|
|
606
|
-
reduced
|
|
1379
|
+
reduced,
|
|
1380
|
+
showSuggestions: i === lastBotIndex && !isLoading && msg.streaming !== true
|
|
607
1381
|
},
|
|
608
1382
|
i
|
|
609
1383
|
)),
|
|
610
|
-
|
|
611
|
-
error && /* @__PURE__ */ (0,
|
|
1384
|
+
waitingForFirstToken && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TypingDots, { reduced }),
|
|
1385
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
612
1386
|
"div",
|
|
613
1387
|
{
|
|
1388
|
+
role: "alert",
|
|
614
1389
|
style: {
|
|
615
1390
|
alignSelf: "flex-start",
|
|
616
1391
|
padding: "10px 14px",
|
|
@@ -623,12 +1398,12 @@ function ChatMessages() {
|
|
|
623
1398
|
children: error
|
|
624
1399
|
}
|
|
625
1400
|
),
|
|
626
|
-
/* @__PURE__ */ (0,
|
|
1401
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: messagesEndRef })
|
|
627
1402
|
] });
|
|
628
1403
|
}
|
|
629
1404
|
|
|
630
1405
|
// src/components/chat-suggestions.tsx
|
|
631
|
-
var
|
|
1406
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
632
1407
|
function ChatSuggestions() {
|
|
633
1408
|
const { messages, isLoading, config, sendMessage } = useChat();
|
|
634
1409
|
const reduced = useReducedMotion();
|
|
@@ -655,7 +1430,7 @@ function ChatSuggestions() {
|
|
|
655
1430
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
656
1431
|
transition: reduced ? "none" : "border-color 0.15s, background 0.15s"
|
|
657
1432
|
};
|
|
658
|
-
return /* @__PURE__ */ (0,
|
|
1433
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: containerStyle, children: config.suggestedMessages.map((text) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
659
1434
|
"button",
|
|
660
1435
|
{
|
|
661
1436
|
style: chipStyle,
|
|
@@ -675,15 +1450,68 @@ function ChatSuggestions() {
|
|
|
675
1450
|
}
|
|
676
1451
|
|
|
677
1452
|
// src/components/chat-input.tsx
|
|
678
|
-
var
|
|
679
|
-
var
|
|
1453
|
+
var import_react8 = require("react");
|
|
1454
|
+
var import_js2 = require("@customerhero/js");
|
|
1455
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1456
|
+
var MAX_ATTACHMENTS = 3;
|
|
680
1457
|
function ChatInput() {
|
|
681
|
-
const { sendMessage, isLoading, config, t } = useChat();
|
|
1458
|
+
const { sendMessage, uploadAttachment, isLoading, config, t } = useChat();
|
|
682
1459
|
const reduced = useReducedMotion();
|
|
683
|
-
const [value, setValue] = (0,
|
|
1460
|
+
const [value, setValue] = (0, import_react8.useState)("");
|
|
1461
|
+
const [attachments, setAttachments] = (0, import_react8.useState)([]);
|
|
1462
|
+
const [captureSupported, setCaptureSupported] = (0, import_react8.useState)(false);
|
|
1463
|
+
(0, import_react8.useEffect)(() => {
|
|
1464
|
+
setCaptureSupported((0, import_js2.canCaptureScreenshot)());
|
|
1465
|
+
}, []);
|
|
1466
|
+
(0, import_react8.useEffect)(() => {
|
|
1467
|
+
return () => {
|
|
1468
|
+
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1469
|
+
};
|
|
1470
|
+
}, []);
|
|
1471
|
+
const updateAttachment = (id, patch) => {
|
|
1472
|
+
setAttachments(
|
|
1473
|
+
(current) => current.map((a) => a.id === id ? { ...a, ...patch } : a)
|
|
1474
|
+
);
|
|
1475
|
+
};
|
|
1476
|
+
const startUpload = async (blob) => {
|
|
1477
|
+
if (attachments.length >= MAX_ATTACHMENTS) return;
|
|
1478
|
+
const id = `att_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1479
|
+
const previewUrl = URL.createObjectURL(blob);
|
|
1480
|
+
setAttachments((current) => [
|
|
1481
|
+
...current,
|
|
1482
|
+
{ id, status: "uploading", previewUrl, blob }
|
|
1483
|
+
]);
|
|
1484
|
+
try {
|
|
1485
|
+
const { attachmentToken } = await uploadAttachment(blob);
|
|
1486
|
+
updateAttachment(id, {
|
|
1487
|
+
status: "ready",
|
|
1488
|
+
token: attachmentToken
|
|
1489
|
+
});
|
|
1490
|
+
} catch {
|
|
1491
|
+
updateAttachment(id, { status: "error" });
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
const handleCapture = async () => {
|
|
1495
|
+
try {
|
|
1496
|
+
const blob = await (0, import_js2.captureScreenshot)();
|
|
1497
|
+
await startUpload(blob);
|
|
1498
|
+
} catch (e) {
|
|
1499
|
+
if (e instanceof import_js2.ScreenshotCancelled) return;
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1502
|
+
const handleRemove = (id) => {
|
|
1503
|
+
setAttachments((current) => {
|
|
1504
|
+
const target = current.find((a) => a.id === id);
|
|
1505
|
+
if (target) URL.revokeObjectURL(target.previewUrl);
|
|
1506
|
+
return current.filter((a) => a.id !== id);
|
|
1507
|
+
});
|
|
1508
|
+
};
|
|
1509
|
+
const readyTokens = attachments.filter((a) => a.status === "ready").map((a) => a.token);
|
|
684
1510
|
const handleSend = () => {
|
|
685
1511
|
if (!value.trim() || isLoading) return;
|
|
686
|
-
sendMessage(value);
|
|
1512
|
+
sendMessage(value, readyTokens.length > 0 ? { attachmentTokens: readyTokens } : void 0);
|
|
1513
|
+
for (const a of attachments) URL.revokeObjectURL(a.previewUrl);
|
|
1514
|
+
setAttachments([]);
|
|
687
1515
|
setValue("");
|
|
688
1516
|
};
|
|
689
1517
|
const handleKeyDown = (e) => {
|
|
@@ -695,6 +1523,11 @@ function ChatInput() {
|
|
|
695
1523
|
const containerStyle = {
|
|
696
1524
|
padding: "12px 16px",
|
|
697
1525
|
borderTop: "1px solid #eee",
|
|
1526
|
+
display: "flex",
|
|
1527
|
+
flexDirection: "column",
|
|
1528
|
+
gap: 8
|
|
1529
|
+
};
|
|
1530
|
+
const rowStyle = {
|
|
698
1531
|
display: "flex",
|
|
699
1532
|
alignItems: "center",
|
|
700
1533
|
gap: 8
|
|
@@ -710,7 +1543,7 @@ function ChatInput() {
|
|
|
710
1543
|
color: config.textColor,
|
|
711
1544
|
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
|
712
1545
|
};
|
|
713
|
-
const
|
|
1546
|
+
const sendButtonStyle = {
|
|
714
1547
|
width: 36,
|
|
715
1548
|
height: 36,
|
|
716
1549
|
borderRadius: "50%",
|
|
@@ -726,57 +1559,210 @@ function ChatInput() {
|
|
|
726
1559
|
flexShrink: 0,
|
|
727
1560
|
transform: "scale(1)"
|
|
728
1561
|
};
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1562
|
+
const iconButtonStyle = (disabled) => ({
|
|
1563
|
+
width: 36,
|
|
1564
|
+
height: 36,
|
|
1565
|
+
borderRadius: "50%",
|
|
1566
|
+
background: "transparent",
|
|
1567
|
+
border: "none",
|
|
1568
|
+
color: disabled ? "#ccc" : "#666",
|
|
1569
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
1570
|
+
display: "flex",
|
|
1571
|
+
alignItems: "center",
|
|
1572
|
+
justifyContent: "center",
|
|
1573
|
+
flexShrink: 0,
|
|
1574
|
+
padding: 0
|
|
1575
|
+
});
|
|
1576
|
+
const captureDisabled = attachments.length >= MAX_ATTACHMENTS || isLoading;
|
|
1577
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: containerStyle, children: [
|
|
1578
|
+
attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1579
|
+
"div",
|
|
732
1580
|
{
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1581
|
+
style: { display: "flex", gap: 8, flexWrap: "wrap" },
|
|
1582
|
+
"aria-label": "Attachments",
|
|
1583
|
+
children: attachments.map((a) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1584
|
+
Thumbnail,
|
|
1585
|
+
{
|
|
1586
|
+
attachment: a,
|
|
1587
|
+
onRemove: () => handleRemove(a.id),
|
|
1588
|
+
t
|
|
1589
|
+
},
|
|
1590
|
+
a.id
|
|
1591
|
+
))
|
|
740
1592
|
}
|
|
741
1593
|
),
|
|
742
|
-
/* @__PURE__ */ (0,
|
|
1594
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: rowStyle, children: [
|
|
1595
|
+
captureSupported && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1596
|
+
"button",
|
|
1597
|
+
{
|
|
1598
|
+
type: "button",
|
|
1599
|
+
onClick: handleCapture,
|
|
1600
|
+
disabled: captureDisabled,
|
|
1601
|
+
style: iconButtonStyle(captureDisabled),
|
|
1602
|
+
"aria-label": t("screenshot_capture"),
|
|
1603
|
+
title: t("screenshot_capture"),
|
|
1604
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CameraIcon, {})
|
|
1605
|
+
}
|
|
1606
|
+
),
|
|
1607
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1608
|
+
"input",
|
|
1609
|
+
{
|
|
1610
|
+
type: "text",
|
|
1611
|
+
value,
|
|
1612
|
+
onChange: (e) => setValue(e.target.value),
|
|
1613
|
+
onKeyDown: handleKeyDown,
|
|
1614
|
+
placeholder: config.placeholderText,
|
|
1615
|
+
style: inputStyle,
|
|
1616
|
+
disabled: isLoading
|
|
1617
|
+
}
|
|
1618
|
+
),
|
|
1619
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1620
|
+
"button",
|
|
1621
|
+
{
|
|
1622
|
+
onClick: handleSend,
|
|
1623
|
+
disabled: isLoading || !value.trim(),
|
|
1624
|
+
style: sendButtonStyle,
|
|
1625
|
+
"aria-label": t("send_message"),
|
|
1626
|
+
onMouseEnter: (e) => {
|
|
1627
|
+
if (!reduced && !isLoading)
|
|
1628
|
+
e.currentTarget.style.transform = "scale(1.1)";
|
|
1629
|
+
},
|
|
1630
|
+
onMouseLeave: (e) => {
|
|
1631
|
+
if (!reduced) e.currentTarget.style.transform = "scale(1)";
|
|
1632
|
+
},
|
|
1633
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1634
|
+
"svg",
|
|
1635
|
+
{
|
|
1636
|
+
width: "16",
|
|
1637
|
+
height: "16",
|
|
1638
|
+
viewBox: "0 0 24 24",
|
|
1639
|
+
fill: "none",
|
|
1640
|
+
stroke: "currentColor",
|
|
1641
|
+
strokeWidth: "2",
|
|
1642
|
+
strokeLinecap: "round",
|
|
1643
|
+
strokeLinejoin: "round",
|
|
1644
|
+
children: [
|
|
1645
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
|
|
1646
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
1647
|
+
]
|
|
1648
|
+
}
|
|
1649
|
+
)
|
|
1650
|
+
}
|
|
1651
|
+
)
|
|
1652
|
+
] })
|
|
1653
|
+
] });
|
|
1654
|
+
}
|
|
1655
|
+
function Thumbnail({
|
|
1656
|
+
attachment,
|
|
1657
|
+
onRemove,
|
|
1658
|
+
t
|
|
1659
|
+
}) {
|
|
1660
|
+
const { status, previewUrl } = attachment;
|
|
1661
|
+
const wrap = {
|
|
1662
|
+
position: "relative",
|
|
1663
|
+
width: 56,
|
|
1664
|
+
height: 56,
|
|
1665
|
+
borderRadius: 8,
|
|
1666
|
+
overflow: "hidden",
|
|
1667
|
+
border: status === "error" ? "2px solid #b91c1c" : "1px solid #e0e0e0",
|
|
1668
|
+
background: "#f5f5f5"
|
|
1669
|
+
};
|
|
1670
|
+
const img = {
|
|
1671
|
+
width: "100%",
|
|
1672
|
+
height: "100%",
|
|
1673
|
+
objectFit: "cover"
|
|
1674
|
+
};
|
|
1675
|
+
const removeBtn = {
|
|
1676
|
+
position: "absolute",
|
|
1677
|
+
top: 2,
|
|
1678
|
+
right: 2,
|
|
1679
|
+
width: 18,
|
|
1680
|
+
height: 18,
|
|
1681
|
+
borderRadius: "50%",
|
|
1682
|
+
border: "none",
|
|
1683
|
+
background: "rgba(0,0,0,0.6)",
|
|
1684
|
+
color: "white",
|
|
1685
|
+
fontSize: 11,
|
|
1686
|
+
lineHeight: "18px",
|
|
1687
|
+
padding: 0,
|
|
1688
|
+
cursor: "pointer",
|
|
1689
|
+
textAlign: "center"
|
|
1690
|
+
};
|
|
1691
|
+
const overlay = {
|
|
1692
|
+
position: "absolute",
|
|
1693
|
+
inset: 0,
|
|
1694
|
+
background: "rgba(255,255,255,0.7)",
|
|
1695
|
+
display: "flex",
|
|
1696
|
+
alignItems: "center",
|
|
1697
|
+
justifyContent: "center"
|
|
1698
|
+
};
|
|
1699
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: wrap, children: [
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: previewUrl, alt: "", style: img }),
|
|
1701
|
+
status === "uploading" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: overlay, "aria-label": "Uploading", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Spinner2, {}) }),
|
|
1702
|
+
status === "error" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1703
|
+
"div",
|
|
1704
|
+
{
|
|
1705
|
+
style: { ...overlay, background: "rgba(255,255,255,0.85)" },
|
|
1706
|
+
"aria-label": t("status_failed"),
|
|
1707
|
+
title: t("status_failed"),
|
|
1708
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: { color: "#b91c1c", fontSize: 11, fontWeight: 600 }, children: "!" })
|
|
1709
|
+
}
|
|
1710
|
+
),
|
|
1711
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
743
1712
|
"button",
|
|
744
1713
|
{
|
|
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
|
-
|
|
1714
|
+
type: "button",
|
|
1715
|
+
style: removeBtn,
|
|
1716
|
+
onClick: onRemove,
|
|
1717
|
+
"aria-label": t("attachment_remove"),
|
|
1718
|
+
children: "\xD7"
|
|
1719
|
+
}
|
|
1720
|
+
)
|
|
1721
|
+
] });
|
|
1722
|
+
}
|
|
1723
|
+
function CameraIcon() {
|
|
1724
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1725
|
+
"svg",
|
|
1726
|
+
{
|
|
1727
|
+
width: "20",
|
|
1728
|
+
height: "20",
|
|
1729
|
+
viewBox: "0 0 24 24",
|
|
1730
|
+
fill: "none",
|
|
1731
|
+
stroke: "currentColor",
|
|
1732
|
+
strokeWidth: "2",
|
|
1733
|
+
strokeLinecap: "round",
|
|
1734
|
+
strokeLinejoin: "round",
|
|
1735
|
+
"aria-hidden": "true",
|
|
1736
|
+
children: [
|
|
1737
|
+
/* @__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" }),
|
|
1738
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("circle", { cx: "12", cy: "13", r: "4" })
|
|
1739
|
+
]
|
|
1740
|
+
}
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
function Spinner2() {
|
|
1744
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
1745
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("style", { children: `@keyframes ch-att-spin { to { transform: rotate(360deg); } }` }),
|
|
1746
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1747
|
+
"span",
|
|
1748
|
+
{
|
|
1749
|
+
"aria-hidden": "true",
|
|
1750
|
+
style: {
|
|
1751
|
+
width: 16,
|
|
1752
|
+
height: 16,
|
|
1753
|
+
borderRadius: "50%",
|
|
1754
|
+
border: "2px solid #888",
|
|
1755
|
+
borderTopColor: "transparent",
|
|
1756
|
+
animation: "ch-att-spin 0.8s linear infinite",
|
|
1757
|
+
display: "inline-block"
|
|
1758
|
+
}
|
|
773
1759
|
}
|
|
774
1760
|
)
|
|
775
1761
|
] });
|
|
776
1762
|
}
|
|
777
1763
|
|
|
778
1764
|
// src/components/chat-window.tsx
|
|
779
|
-
var
|
|
1765
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
780
1766
|
function ConfigError({ message, title }) {
|
|
781
1767
|
const errorStyle = {
|
|
782
1768
|
flex: 1,
|
|
@@ -788,8 +1774,8 @@ function ConfigError({ message, title }) {
|
|
|
788
1774
|
textAlign: "center",
|
|
789
1775
|
gap: 8
|
|
790
1776
|
};
|
|
791
|
-
return /* @__PURE__ */ (0,
|
|
792
|
-
/* @__PURE__ */ (0,
|
|
1777
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: errorStyle, children: [
|
|
1778
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
793
1779
|
"svg",
|
|
794
1780
|
{
|
|
795
1781
|
width: "32",
|
|
@@ -801,22 +1787,22 @@ function ConfigError({ message, title }) {
|
|
|
801
1787
|
strokeLinecap: "round",
|
|
802
1788
|
strokeLinejoin: "round",
|
|
803
1789
|
children: [
|
|
804
|
-
/* @__PURE__ */ (0,
|
|
805
|
-
/* @__PURE__ */ (0,
|
|
806
|
-
/* @__PURE__ */ (0,
|
|
1790
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1791
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
1792
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
807
1793
|
]
|
|
808
1794
|
}
|
|
809
1795
|
),
|
|
810
|
-
/* @__PURE__ */ (0,
|
|
811
|
-
/* @__PURE__ */ (0,
|
|
1796
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 14, fontWeight: 500, color: "#333", margin: 0 }, children: title }),
|
|
1797
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
|
|
812
1798
|
] });
|
|
813
1799
|
}
|
|
814
1800
|
function ChatWindow() {
|
|
815
|
-
const { isOpen, config, configError, t } = useChat();
|
|
1801
|
+
const { isOpen, config, configError, t, isRtl } = useChat();
|
|
816
1802
|
const reduced = useReducedMotion();
|
|
817
|
-
const [visible, setVisible] = (0,
|
|
818
|
-
const [shouldRender, setShouldRender] = (0,
|
|
819
|
-
(0,
|
|
1803
|
+
const [visible, setVisible] = (0, import_react9.useState)(false);
|
|
1804
|
+
const [shouldRender, setShouldRender] = (0, import_react9.useState)(false);
|
|
1805
|
+
(0, import_react9.useEffect)(() => {
|
|
820
1806
|
if (isOpen) {
|
|
821
1807
|
setShouldRender(true);
|
|
822
1808
|
requestAnimationFrame(() => {
|
|
@@ -833,10 +1819,11 @@ function ChatWindow() {
|
|
|
833
1819
|
}
|
|
834
1820
|
}, [isOpen, reduced]);
|
|
835
1821
|
if (!shouldRender) return null;
|
|
1822
|
+
const effectivePosition = isRtl ? config.position === "bottom-right" ? "bottom-left" : "bottom-right" : config.position;
|
|
836
1823
|
const style = {
|
|
837
1824
|
position: "fixed",
|
|
838
1825
|
bottom: 90,
|
|
839
|
-
[
|
|
1826
|
+
[effectivePosition === "bottom-left" ? "left" : "right"]: 20,
|
|
840
1827
|
width: 380,
|
|
841
1828
|
maxWidth: "calc(100vw - 40px)",
|
|
842
1829
|
height: 520,
|
|
@@ -864,17 +1851,17 @@ function ChatWindow() {
|
|
|
864
1851
|
textDecoration: "underline",
|
|
865
1852
|
textUnderlineOffset: 2
|
|
866
1853
|
};
|
|
867
|
-
return /* @__PURE__ */ (0,
|
|
868
|
-
/* @__PURE__ */ (0,
|
|
869
|
-
configError ? /* @__PURE__ */ (0,
|
|
870
|
-
/* @__PURE__ */ (0,
|
|
871
|
-
/* @__PURE__ */ (0,
|
|
872
|
-
/* @__PURE__ */ (0,
|
|
1854
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style, dir: isRtl ? "rtl" : "ltr", children: [
|
|
1855
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatHeader, {}),
|
|
1856
|
+
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: [
|
|
1857
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatMessages, {}),
|
|
1858
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatSuggestions, {}),
|
|
1859
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatInput, {})
|
|
873
1860
|
] }),
|
|
874
|
-
/* @__PURE__ */ (0,
|
|
1861
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: poweredStyle, children: [
|
|
875
1862
|
t("powered_by"),
|
|
876
1863
|
" ",
|
|
877
|
-
/* @__PURE__ */ (0,
|
|
1864
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
878
1865
|
"a",
|
|
879
1866
|
{
|
|
880
1867
|
href: "https://customerhero.app",
|
|
@@ -889,11 +1876,11 @@ function ChatWindow() {
|
|
|
889
1876
|
}
|
|
890
1877
|
|
|
891
1878
|
// src/components/chat-widget.tsx
|
|
892
|
-
var
|
|
1879
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
893
1880
|
function ChatWidgetInner({ identity }) {
|
|
894
1881
|
const client = useCustomerHeroClient();
|
|
895
|
-
const prevIdentityRef = (0,
|
|
896
|
-
(0,
|
|
1882
|
+
const prevIdentityRef = (0, import_react10.useRef)(void 0);
|
|
1883
|
+
(0, import_react10.useEffect)(() => {
|
|
897
1884
|
const key = identity ? JSON.stringify(identity) : void 0;
|
|
898
1885
|
if (key !== prevIdentityRef.current) {
|
|
899
1886
|
prevIdentityRef.current = key;
|
|
@@ -902,15 +1889,17 @@ function ChatWidgetInner({ identity }) {
|
|
|
902
1889
|
}
|
|
903
1890
|
}
|
|
904
1891
|
}, [identity, client]);
|
|
905
|
-
return /* @__PURE__ */ (0,
|
|
906
|
-
/* @__PURE__ */ (0,
|
|
907
|
-
/* @__PURE__ */ (0,
|
|
1892
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
1893
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatBubble, {}),
|
|
1894
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatWindow, {})
|
|
908
1895
|
] });
|
|
909
1896
|
}
|
|
910
1897
|
function ChatWidget({ identity, ...config }) {
|
|
911
|
-
return /* @__PURE__ */ (0,
|
|
1898
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatWidgetInner, { identity }) });
|
|
912
1899
|
}
|
|
913
1900
|
// Annotate the CommonJS export names for ESM import in node:
|
|
914
1901
|
0 && (module.exports = {
|
|
915
|
-
|
|
1902
|
+
ActionConfirmationCard,
|
|
1903
|
+
ChatWidget,
|
|
1904
|
+
useChat
|
|
916
1905
|
});
|