@customerhero/react 0.0.1 → 1.0.0

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