@paymanai/payman-ask-sdk 1.0.4 → 1.1.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.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import { createContext, useContext, useState, useMemo, useRef, useCallback, useEffect } from 'react';
2
2
  import { useChat } from '@paymanai/payman-typescript-ask-sdk';
3
- export { generateId, streamWorkflowEvents, useChat } from '@paymanai/payman-typescript-ask-sdk';
3
+ export { cancelUserAction, generateId, resendUserAction, streamWorkflowEvents, submitUserAction, useChat } from '@paymanai/payman-typescript-ask-sdk';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { motion, AnimatePresence } from 'framer-motion';
7
- import { Bot, Pause, Send, User, Loader2, X, ChevronUp, ChevronDown, XCircle, Check, Binoculars } from 'lucide-react';
8
- import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { Bot, Pause, Send, ShieldCheck, Loader2, User, CheckCircle2, XCircle, X, ChevronUp, ChevronDown, Check, Binoculars } from 'lucide-react';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
9
  import ReactMarkdown from 'react-markdown';
10
10
  import remarkGfm from 'remark-gfm';
11
11
 
@@ -159,7 +159,6 @@ function AgentMessage({
159
159
  agentName = "Assistant",
160
160
  showAvatar = false,
161
161
  layout = "full-width",
162
- showTimestamp = false,
163
162
  showExecutionSteps = false,
164
163
  showStreamingDot = false,
165
164
  streamingStepsText,
@@ -212,6 +211,13 @@ function AgentMessage({
212
211
  "min-w-0 flex flex-col",
213
212
  layout === "centered" ? "max-w-[85%]" : showAvatar ? "max-w-[85%]" : "max-w-[80%]"
214
213
  ), children: [
214
+ message.userActionResult && /* @__PURE__ */ jsx("div", { className: "mb-2", children: message.userActionResult === "approved" ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-700", children: [
215
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "w-3 h-3" }),
216
+ " OTP Verified"
217
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-full bg-red-100 text-red-700", children: [
218
+ /* @__PURE__ */ jsx(XCircle, { className: "w-3 h-3" }),
219
+ " Rejected"
220
+ ] }) }),
215
221
  /* @__PURE__ */ jsxs(
216
222
  "div",
217
223
  {
@@ -319,7 +325,7 @@ function AgentMessage({
319
325
  layout === "centered" ? "text-neutral-500 hover:bg-neutral-50" : "text-muted-foreground hover:bg-muted/70"
320
326
  ),
321
327
  children: [
322
- /* @__PURE__ */ jsx("span", { children: getCompletedStepsText(isStepsExpanded) }),
328
+ /* @__PURE__ */ jsx("span", { children: getCompletedStepsText(!!isStepsExpanded) }),
323
329
  isStepsExpanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3" })
324
330
  ]
325
331
  }
@@ -339,7 +345,7 @@ function AgentMessage({
339
345
  step.status === "pending" && isCancelled && /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3 text-muted-foreground shrink-0 mt-0.5" }),
340
346
  step.status === "pending" && !isCancelled && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 text-primary animate-spin shrink-0 mt-0.5" }),
341
347
  step.status === "in_progress" && !isCurrentlyExecuting && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 text-primary animate-spin shrink-0 mt-0.5" }),
342
- step.status === "completed" && /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 text-primary shrink-0 mt-0.5" }),
348
+ step.status === "completed" && /* @__PURE__ */ jsx(Check, { className: cn("h-3 w-3 shrink-0 mt-0.5", step.eventType === "USER_ACTION_SUCCESS" ? "text-green-600" : "text-primary") }),
343
349
  step.status === "error" && /* @__PURE__ */ jsx(X, { className: "h-3 w-3 text-destructive shrink-0 mt-0.5" }),
344
350
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
345
351
  /* @__PURE__ */ jsx(
@@ -347,7 +353,8 @@ function AgentMessage({
347
353
  {
348
354
  className: cn(
349
355
  "text-muted-foreground break-words",
350
- step.status === "error" && "text-destructive"
356
+ step.status === "error" && "text-destructive",
357
+ step.eventType === "USER_ACTION_SUCCESS" && "text-green-600"
351
358
  ),
352
359
  children: step.message
353
360
  }
@@ -376,7 +383,7 @@ function AgentMessage({
376
383
  layout === "centered" ? "text-neutral-500 hover:bg-neutral-50" : "text-muted-foreground hover:bg-muted/70"
377
384
  ),
378
385
  children: [
379
- /* @__PURE__ */ jsx("span", { children: getStreamingStepsText(isStepsExpanded) }),
386
+ /* @__PURE__ */ jsx("span", { children: getStreamingStepsText(!!isStepsExpanded) }),
380
387
  isStepsExpanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3" })
381
388
  ]
382
389
  }
@@ -396,7 +403,7 @@ function AgentMessage({
396
403
  step.status === "pending" && isCancelled && /* @__PURE__ */ jsx(XCircle, { className: "h-3 w-3 text-muted-foreground shrink-0 mt-0.5" }),
397
404
  step.status === "pending" && !isCancelled && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 text-primary animate-spin shrink-0 mt-0.5" }),
398
405
  step.status === "in_progress" && !isCurrentlyExecuting && /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 text-primary animate-spin shrink-0 mt-0.5" }),
399
- step.status === "completed" && /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 text-primary shrink-0 mt-0.5" }),
406
+ step.status === "completed" && /* @__PURE__ */ jsx(Check, { className: cn("h-3 w-3 shrink-0 mt-0.5", step.eventType === "USER_ACTION_SUCCESS" ? "text-green-600" : "text-primary") }),
400
407
  step.status === "error" && /* @__PURE__ */ jsx(X, { className: "h-3 w-3 text-destructive shrink-0 mt-0.5" }),
401
408
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
402
409
  /* @__PURE__ */ jsx(
@@ -404,7 +411,8 @@ function AgentMessage({
404
411
  {
405
412
  className: cn(
406
413
  "text-muted-foreground break-words",
407
- step.status === "error" && "text-destructive"
414
+ step.status === "error" && "text-destructive",
415
+ step.eventType === "USER_ACTION_SUCCESS" && "text-green-600"
408
416
  ),
409
417
  children: step.message
410
418
  }
@@ -429,7 +437,10 @@ function AgentMessage({
429
437
  tracingData: message.tracingData,
430
438
  executionId: message.executionId
431
439
  }),
432
- className: "mt-0.5 p-2 bg-white/5 border border-white/20 rounded-lg shadow-sm hover:bg-white/10 hover:border-highlight transition-colors duration-200 flex-shrink-0",
440
+ className: cn(
441
+ "p-2 bg-white/5 border border-white/20 rounded-lg shadow-sm hover:bg-white/10 hover:border-highlight transition-colors duration-200 flex-shrink-0",
442
+ message.userActionResult ? "mt-8" : "mt-0.5"
443
+ ),
433
444
  title: "View execution trace",
434
445
  "aria-label": "View execution trace",
435
446
  children: /* @__PURE__ */ jsx(Binoculars, { className: "w-4 h-4 text-foreground" })
@@ -649,6 +660,466 @@ function MessageList({
649
660
  }
650
661
  );
651
662
  }
663
+ var DEFAULT_MAX_LENGTH = 6;
664
+ var MAX_SUPPORTED_LENGTH = 12;
665
+ var AUTO_FOCUS_DELAY_MS = 250;
666
+ function OtpInput({ value, onChange, maxLength, disabled = false }) {
667
+ const inputRefs = useRef([]);
668
+ const safeMaxLength = Number.isInteger(maxLength) && maxLength > 0 ? Math.min(maxLength, MAX_SUPPORTED_LENGTH) : DEFAULT_MAX_LENGTH;
669
+ const digits = value.split("").concat(Array(safeMaxLength).fill("")).slice(0, safeMaxLength);
670
+ useEffect(() => {
671
+ if (disabled) return;
672
+ const focusTimer = window.setTimeout(() => {
673
+ inputRefs.current[0]?.focus();
674
+ }, AUTO_FOCUS_DELAY_MS);
675
+ return () => window.clearTimeout(focusTimer);
676
+ }, [disabled]);
677
+ const focusInput = (index) => {
678
+ if (index >= 0 && index < safeMaxLength) {
679
+ inputRefs.current[index]?.focus();
680
+ }
681
+ };
682
+ const updateValue = (newDigits) => {
683
+ onChange(newDigits.join("").slice(0, safeMaxLength));
684
+ };
685
+ const handleChange = (index, char) => {
686
+ if (char && !/^\d$/.test(char)) return;
687
+ const newDigits = [...digits];
688
+ newDigits[index] = char;
689
+ updateValue(newDigits);
690
+ if (char && index < safeMaxLength - 1) {
691
+ focusInput(index + 1);
692
+ }
693
+ };
694
+ const handleKeyDown = (index, e) => {
695
+ if (e.key === "Enter") {
696
+ e.preventDefault();
697
+ return;
698
+ }
699
+ if (e.key === "Backspace") {
700
+ e.preventDefault();
701
+ if (digits[index]) {
702
+ const newDigits = [...digits];
703
+ newDigits[index] = "";
704
+ updateValue(newDigits);
705
+ } else if (index > 0) {
706
+ const newDigits = [...digits];
707
+ newDigits[index - 1] = "";
708
+ updateValue(newDigits);
709
+ focusInput(index - 1);
710
+ }
711
+ return;
712
+ }
713
+ if (e.key === "ArrowLeft") {
714
+ e.preventDefault();
715
+ focusInput(index - 1);
716
+ } else if (e.key === "ArrowRight") {
717
+ e.preventDefault();
718
+ focusInput(index + 1);
719
+ }
720
+ };
721
+ const handlePaste = (e) => {
722
+ e.preventDefault();
723
+ const pasted = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, safeMaxLength);
724
+ if (!pasted) return;
725
+ const newDigits = pasted.split("").concat(Array(safeMaxLength).fill("")).slice(0, safeMaxLength);
726
+ updateValue(newDigits);
727
+ focusInput(Math.min(pasted.length, safeMaxLength - 1));
728
+ };
729
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "8px", justifyContent: "center" }, children: digits.map((digit, i) => /* @__PURE__ */ jsx(
730
+ "input",
731
+ {
732
+ ref: (el) => {
733
+ inputRefs.current[i] = el;
734
+ },
735
+ type: "text",
736
+ inputMode: "numeric",
737
+ maxLength: 1,
738
+ value: digit,
739
+ disabled,
740
+ onChange: (e) => handleChange(i, e.target.value.slice(-1)),
741
+ onKeyDown: (e) => handleKeyDown(i, e),
742
+ onPaste: handlePaste,
743
+ onFocus: (e) => e.target.select(),
744
+ "aria-label": `Digit ${i + 1}`,
745
+ style: {
746
+ width: "40px",
747
+ height: "48px",
748
+ textAlign: "center",
749
+ fontSize: "20px",
750
+ fontWeight: 600,
751
+ border: "1px solid var(--border, #E5E5E5)",
752
+ borderRadius: "8px",
753
+ outline: "none",
754
+ backgroundColor: disabled ? "var(--muted, #F5F5F5)" : "transparent",
755
+ color: "var(--foreground, #000)",
756
+ opacity: disabled ? 0.5 : 1
757
+ }
758
+ },
759
+ i
760
+ )) });
761
+ }
762
+
763
+ // src/components/UserActionModal/constants.ts
764
+ var BUTTON_LABELS = {
765
+ APPROVE: "Verify",
766
+ REJECT: "Reject",
767
+ RESEND: "Resend OTP"
768
+ };
769
+ var RESEND_OTP_COOLDOWN_SECONDS = 30;
770
+ var DEFAULT_OTP_MAX_LENGTH = 6;
771
+ var MIN_OTP_MAX_LENGTH = 1;
772
+ var MAX_OTP_MAX_LENGTH = 12;
773
+ var ACTION_PENDING_TIMEOUT_MS = 15e3;
774
+ var MODAL_CONTENT = {
775
+ TITLE: "Verification Required",
776
+ LOADING_APPROVE: "Verifying...",
777
+ LOADING_REJECT: "Rejecting...",
778
+ LOADING_RESEND: "Resending...",
779
+ RESEND_AVAILABLE_IN: "Resend OTP in"
780
+ };
781
+
782
+ // src/components/UserActionModal/utils.ts
783
+ function getOtpSchemaFromRequest(schema) {
784
+ const properties = schema?.properties;
785
+ const otp = properties?.otp;
786
+ const maxLengthRaw = otp?.maxLength;
787
+ const parsedMaxLength = Number.isInteger(maxLengthRaw) ? Number(maxLengthRaw) : DEFAULT_OTP_MAX_LENGTH;
788
+ const clampedMaxLength = Math.min(
789
+ MAX_OTP_MAX_LENGTH,
790
+ Math.max(MIN_OTP_MAX_LENGTH, parsedMaxLength)
791
+ );
792
+ return {
793
+ maxLength: clampedMaxLength
794
+ };
795
+ }
796
+ function UserActionModal({
797
+ isOpen,
798
+ userActionRequest,
799
+ onApprove,
800
+ onReject,
801
+ onResend,
802
+ clearOtpTrigger
803
+ }) {
804
+ const [otp, setOtp] = useState("");
805
+ const [actionType, setActionType] = useState(null);
806
+ const [isSubmitting, setIsSubmitting] = useState(false);
807
+ const [resendCooldownRemaining, setResendCooldownRemaining] = useState(0);
808
+ const dialogRef = useRef(null);
809
+ const previousFocusedRef = useRef(null);
810
+ const schema = getOtpSchemaFromRequest(userActionRequest?.requestedSchema);
811
+ const resetActionState = useCallback(() => {
812
+ setIsSubmitting(false);
813
+ setActionType(null);
814
+ }, []);
815
+ useEffect(() => {
816
+ if (isOpen) {
817
+ setResendCooldownRemaining(RESEND_OTP_COOLDOWN_SECONDS);
818
+ } else {
819
+ setOtp("");
820
+ resetActionState();
821
+ setResendCooldownRemaining(0);
822
+ }
823
+ }, [isOpen, resetActionState]);
824
+ useEffect(() => {
825
+ if (resendCooldownRemaining <= 0) return;
826
+ const timer = setTimeout(() => {
827
+ setResendCooldownRemaining((prev) => prev - 1);
828
+ }, 1e3);
829
+ return () => clearTimeout(timer);
830
+ }, [resendCooldownRemaining]);
831
+ useEffect(() => {
832
+ if (clearOtpTrigger > 0) {
833
+ setOtp("");
834
+ resetActionState();
835
+ }
836
+ }, [clearOtpTrigger, resetActionState]);
837
+ useEffect(() => {
838
+ if (!isOpen || !isSubmitting) return;
839
+ if (actionType !== "approve" && actionType !== "reject") return;
840
+ const timeout = setTimeout(() => {
841
+ resetActionState();
842
+ }, ACTION_PENDING_TIMEOUT_MS);
843
+ return () => clearTimeout(timeout);
844
+ }, [isOpen, isSubmitting, actionType, resetActionState]);
845
+ useEffect(() => {
846
+ if (!isOpen) return;
847
+ const dialog = dialogRef.current;
848
+ if (!dialog) return;
849
+ previousFocusedRef.current = document.activeElement;
850
+ const originalOverflow = document.body.style.overflow;
851
+ document.body.style.overflow = "hidden";
852
+ const getFocusableElements = () => Array.from(
853
+ dialog.querySelectorAll(
854
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
855
+ )
856
+ ).filter((el) => !el.hasAttribute("disabled") && el.tabIndex !== -1);
857
+ const focusables = getFocusableElements();
858
+ (focusables[0] ?? dialog).focus();
859
+ const handleKeyDown = (event) => {
860
+ if (event.key === "Escape") {
861
+ event.preventDefault();
862
+ return;
863
+ }
864
+ if (event.key !== "Tab") return;
865
+ const currentFocusables = getFocusableElements();
866
+ if (currentFocusables.length === 0) {
867
+ event.preventDefault();
868
+ dialog.focus();
869
+ return;
870
+ }
871
+ const first = currentFocusables[0];
872
+ const last = currentFocusables[currentFocusables.length - 1];
873
+ const activeElement = document.activeElement;
874
+ const isInDialog = activeElement ? dialog.contains(activeElement) : false;
875
+ if (event.shiftKey) {
876
+ if (!isInDialog || activeElement === first) {
877
+ event.preventDefault();
878
+ last.focus();
879
+ }
880
+ return;
881
+ }
882
+ if (!isInDialog || activeElement === last) {
883
+ event.preventDefault();
884
+ first.focus();
885
+ }
886
+ };
887
+ document.addEventListener("keydown", handleKeyDown);
888
+ return () => {
889
+ document.removeEventListener("keydown", handleKeyDown);
890
+ document.body.style.overflow = originalOverflow;
891
+ previousFocusedRef.current?.focus();
892
+ };
893
+ }, [isOpen]);
894
+ const handleApprove = useCallback(async () => {
895
+ if (otp.length !== schema.maxLength || !/^\d+$/.test(otp)) return;
896
+ setIsSubmitting(true);
897
+ setActionType("approve");
898
+ try {
899
+ await onApprove(otp);
900
+ } catch {
901
+ resetActionState();
902
+ }
903
+ }, [otp, schema.maxLength, onApprove, resetActionState]);
904
+ const handleReject = useCallback(async () => {
905
+ setIsSubmitting(true);
906
+ setActionType("reject");
907
+ try {
908
+ await onReject();
909
+ } catch {
910
+ resetActionState();
911
+ }
912
+ }, [onReject, resetActionState]);
913
+ const handleResend = useCallback(async () => {
914
+ if (resendCooldownRemaining > 0) return;
915
+ setIsSubmitting(true);
916
+ setActionType("resend");
917
+ try {
918
+ await onResend();
919
+ setResendCooldownRemaining(RESEND_OTP_COOLDOWN_SECONDS);
920
+ } catch {
921
+ } finally {
922
+ setActionType(null);
923
+ setIsSubmitting(false);
924
+ }
925
+ }, [resendCooldownRemaining, onResend]);
926
+ if (!isOpen || !userActionRequest) return null;
927
+ const isOtpValid = otp.length === schema.maxLength && /^\d+$/.test(otp);
928
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
929
+ "div",
930
+ {
931
+ style: {
932
+ position: "fixed",
933
+ inset: 0,
934
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
935
+ zIndex: 1e4,
936
+ display: "flex",
937
+ alignItems: "center",
938
+ justifyContent: "center"
939
+ },
940
+ children: /* @__PURE__ */ jsxs(
941
+ "div",
942
+ {
943
+ ref: dialogRef,
944
+ role: "dialog",
945
+ "aria-modal": "true",
946
+ style: {
947
+ backgroundColor: "var(--card, #FFFFFF)",
948
+ borderRadius: "12px",
949
+ padding: "24px",
950
+ width: "100%",
951
+ maxWidth: "420px",
952
+ margin: "0 16px",
953
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)"
954
+ },
955
+ tabIndex: -1,
956
+ children: [
957
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", marginBottom: "16px" }, children: [
958
+ /* @__PURE__ */ jsx(
959
+ "div",
960
+ {
961
+ style: {
962
+ display: "inline-flex",
963
+ alignItems: "center",
964
+ justifyContent: "center",
965
+ width: "48px",
966
+ height: "48px",
967
+ borderRadius: "50%",
968
+ backgroundColor: "var(--muted, #F5F5F5)",
969
+ marginBottom: "12px"
970
+ },
971
+ children: /* @__PURE__ */ jsx(
972
+ ShieldCheck,
973
+ {
974
+ className: "text-primary",
975
+ style: { width: "24px", height: "24px" }
976
+ }
977
+ )
978
+ }
979
+ ),
980
+ /* @__PURE__ */ jsx(
981
+ "h2",
982
+ {
983
+ style: {
984
+ fontSize: "18px",
985
+ fontWeight: 600,
986
+ color: "var(--foreground, #000)",
987
+ margin: 0
988
+ },
989
+ children: MODAL_CONTENT.TITLE
990
+ }
991
+ )
992
+ ] }),
993
+ /* @__PURE__ */ jsx(
994
+ "p",
995
+ {
996
+ style: {
997
+ fontSize: "14px",
998
+ color: "var(--muted-foreground, #666)",
999
+ textAlign: "center",
1000
+ margin: "0 0 24px 0",
1001
+ lineHeight: 1.5
1002
+ },
1003
+ children: userActionRequest.message
1004
+ }
1005
+ ),
1006
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: "24px" }, children: /* @__PURE__ */ jsx(
1007
+ OtpInput,
1008
+ {
1009
+ value: otp,
1010
+ onChange: setOtp,
1011
+ maxLength: schema.maxLength,
1012
+ disabled: isSubmitting
1013
+ }
1014
+ ) }),
1015
+ /* @__PURE__ */ jsxs(
1016
+ "div",
1017
+ {
1018
+ style: {
1019
+ display: "flex",
1020
+ flexDirection: "column",
1021
+ gap: "10px"
1022
+ },
1023
+ children: [
1024
+ /* @__PURE__ */ jsxs(
1025
+ "div",
1026
+ {
1027
+ style: {
1028
+ display: "flex",
1029
+ gap: "8px"
1030
+ },
1031
+ children: [
1032
+ /* @__PURE__ */ jsxs(
1033
+ "button",
1034
+ {
1035
+ onClick: handleReject,
1036
+ disabled: isSubmitting,
1037
+ className: cn(
1038
+ "py-2.5 px-4 rounded-lg text-sm font-medium transition-colors",
1039
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 hover:text-destructive",
1040
+ "disabled:opacity-50 disabled:cursor-not-allowed"
1041
+ ),
1042
+ style: {
1043
+ flex: 1,
1044
+ display: "flex",
1045
+ alignItems: "center",
1046
+ justifyContent: "center",
1047
+ gap: "6px",
1048
+ whiteSpace: "nowrap",
1049
+ minWidth: 0
1050
+ },
1051
+ children: [
1052
+ actionType === "reject" && /* @__PURE__ */ jsx(Loader2, { className: "w-4 h-4 animate-spin", style: { flexShrink: 0 } }),
1053
+ actionType === "reject" ? MODAL_CONTENT.LOADING_REJECT : BUTTON_LABELS.REJECT
1054
+ ]
1055
+ }
1056
+ ),
1057
+ /* @__PURE__ */ jsxs(
1058
+ "button",
1059
+ {
1060
+ onClick: handleApprove,
1061
+ disabled: !isOtpValid || isSubmitting,
1062
+ className: cn(
1063
+ "py-2.5 px-4 rounded-lg text-sm font-medium transition-colors",
1064
+ "bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80",
1065
+ "disabled:opacity-50 disabled:cursor-not-allowed"
1066
+ ),
1067
+ style: {
1068
+ flex: 1,
1069
+ display: "flex",
1070
+ alignItems: "center",
1071
+ justifyContent: "center",
1072
+ gap: "6px",
1073
+ whiteSpace: "nowrap",
1074
+ minWidth: 0
1075
+ },
1076
+ children: [
1077
+ actionType === "approve" && /* @__PURE__ */ jsx(Loader2, { className: "w-4 h-4 animate-spin", style: { flexShrink: 0 } }),
1078
+ actionType === "approve" ? MODAL_CONTENT.LOADING_APPROVE : BUTTON_LABELS.APPROVE
1079
+ ]
1080
+ }
1081
+ )
1082
+ ]
1083
+ }
1084
+ ),
1085
+ /* @__PURE__ */ jsxs(
1086
+ "button",
1087
+ {
1088
+ onClick: handleResend,
1089
+ disabled: isSubmitting || resendCooldownRemaining > 0,
1090
+ className: cn(
1091
+ "py-2 px-4 rounded-lg text-xs font-medium transition-colors w-full",
1092
+ "text-muted-foreground hover:text-foreground",
1093
+ "disabled:opacity-50 disabled:cursor-not-allowed"
1094
+ ),
1095
+ style: {
1096
+ display: "flex",
1097
+ alignItems: "center",
1098
+ justifyContent: "center",
1099
+ gap: "6px",
1100
+ whiteSpace: "nowrap"
1101
+ },
1102
+ children: [
1103
+ actionType === "resend" && /* @__PURE__ */ jsx(Loader2, { className: "w-3.5 h-3.5 animate-spin", style: { flexShrink: 0 } }),
1104
+ actionType === "resend" ? MODAL_CONTENT.LOADING_RESEND : resendCooldownRemaining > 0 ? `${MODAL_CONTENT.RESEND_AVAILABLE_IN} ${resendCooldownRemaining}s` : BUTTON_LABELS.RESEND
1105
+ ]
1106
+ }
1107
+ )
1108
+ ]
1109
+ }
1110
+ )
1111
+ ]
1112
+ }
1113
+ )
1114
+ }
1115
+ ) });
1116
+ }
1117
+ var DEFAULT_USER_ACTION_STATE = {
1118
+ request: null,
1119
+ clearOtpTrigger: 0
1120
+ };
1121
+ var NOOP_ASYNC = async () => {
1122
+ };
652
1123
  function PaymanChat({
653
1124
  config,
654
1125
  callbacks = {},
@@ -657,6 +1128,7 @@ function PaymanChat({
657
1128
  children
658
1129
  }) {
659
1130
  const [inputValue, setInputValue] = useState("");
1131
+ const chat = useChat(config, callbacks);
660
1132
  const {
661
1133
  messages,
662
1134
  sendMessage,
@@ -666,7 +1138,12 @@ function PaymanChat({
666
1138
  cancelStream,
667
1139
  getSessionId,
668
1140
  getMessages
669
- } = useChat(config, callbacks);
1141
+ } = chat;
1142
+ const userActionState = chat.userActionState ?? DEFAULT_USER_ACTION_STATE;
1143
+ const approveUserAction = chat.approveUserAction ?? NOOP_ASYNC;
1144
+ const rejectUserAction = chat.rejectUserAction ?? NOOP_ASYNC;
1145
+ const resendOtp = chat.resendOtp ?? NOOP_ASYNC;
1146
+ const isUserActionSupported = typeof chat.approveUserAction === "function" && typeof chat.rejectUserAction === "function" && typeof chat.resendOtp === "function";
670
1147
  const contextValue = useMemo(
671
1148
  () => ({
672
1149
  resetSession,
@@ -701,6 +1178,7 @@ function PaymanChat({
701
1178
  agentName = "Assistant",
702
1179
  showExecutionSteps = true,
703
1180
  showStreamingDot = true,
1181
+ inputStyle = "rounded",
704
1182
  layout = "full-width",
705
1183
  showTimestamps = false,
706
1184
  animated = true,
@@ -795,7 +1273,20 @@ function PaymanChat({
795
1273
  placeholder,
796
1274
  isWaitingForResponse,
797
1275
  hasSelectedSession: true,
798
- isSessionParamsConfigured
1276
+ isSessionParamsConfigured,
1277
+ inputStyle,
1278
+ layout
1279
+ }
1280
+ ),
1281
+ /* @__PURE__ */ jsx(
1282
+ UserActionModal,
1283
+ {
1284
+ isOpen: isUserActionSupported && userActionState.request !== null,
1285
+ userActionRequest: userActionState.request,
1286
+ onApprove: approveUserAction,
1287
+ onReject: rejectUserAction,
1288
+ onResend: resendOtp,
1289
+ clearOtpTrigger: userActionState.clearOtpTrigger
799
1290
  }
800
1291
  )
801
1292
  ]