@paymanai/payman-ask-sdk 1.1.1 → 1.2.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,10 +1,10 @@
1
- import { createContext, useContext, useState, useMemo, useRef, useCallback, useEffect } from 'react';
2
- import { useChat } from '@paymanai/payman-typescript-ask-sdk';
3
- export { cancelUserAction, generateId, resendUserAction, streamWorkflowEvents, submitUserAction, useChat } from '@paymanai/payman-typescript-ask-sdk';
1
+ import { useChat, useVoice } from '@paymanai/payman-typescript-ask-sdk';
2
+ export { cancelUserAction, generateId, resendUserAction, streamWorkflowEvents, submitUserAction, useChat, useVoice } from '@paymanai/payman-typescript-ask-sdk';
3
+ import { createContext, useContext, useState, useRef, useMemo, useEffect, useCallback } from 'react';
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, ShieldCheck, Loader2, User, CheckCircle2, XCircle, X, ChevronUp, ChevronDown, Check, Binoculars } from 'lucide-react';
7
+ import { Bot, Mic, Pause, Send, ShieldCheck, Loader2, User, CheckCircle2, XCircle, X, ChevronUp, ChevronDown, Check, Binoculars } from 'lucide-react';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
9
  import ReactMarkdown from 'react-markdown';
10
10
  import remarkGfm from 'remark-gfm';
@@ -58,7 +58,11 @@ function ChatInput({
58
58
  hasSelectedSession = true,
59
59
  isSessionParamsConfigured = true,
60
60
  onClick,
61
- className
61
+ className,
62
+ enableVoice = false,
63
+ onVoicePress,
64
+ voiceAvailable = false,
65
+ isRecording = false
62
66
  }) {
63
67
  const textareaRef = useRef(null);
64
68
  useEffect(() => {
@@ -77,6 +81,8 @@ function ChatInput({
77
81
  };
78
82
  const isInputDisabled = disabled || isWaitingForResponse;
79
83
  const showPauseButton = isWaitingForResponse && onPause;
84
+ const showVoiceButton = enableVoice && onVoicePress != null;
85
+ const isVoiceButtonDisabled = isWaitingForResponse || !voiceAvailable || !isSessionParamsConfigured;
80
86
  const getPlaceholder = () => {
81
87
  if (!hasSelectedSession) {
82
88
  return "Select a version to start chatting";
@@ -90,7 +96,7 @@ function ChatInput({
90
96
  motion.div,
91
97
  {
92
98
  initial: false,
93
- className: "relative flex items-end overflow-hidden transition-colors bg-input border border-border shadow-sm",
99
+ className: "flex items-end overflow-hidden transition-colors bg-input border border-border shadow-sm",
94
100
  style: { borderRadius: "24px" },
95
101
  children: [
96
102
  /* @__PURE__ */ jsx(
@@ -103,51 +109,48 @@ function ChatInput({
103
109
  onClick,
104
110
  disabled: isInputDisabled,
105
111
  placeholder: getPlaceholder(),
106
- className: "bg-transparent text-foreground focus:outline-none resize-none overflow-y-auto disabled:opacity-50 disabled:cursor-not-allowed w-full text-sm placeholder:text-muted-foreground",
112
+ className: "bg-transparent text-foreground focus:outline-none resize-none overflow-y-auto disabled:opacity-50 disabled:cursor-not-allowed flex-1 min-w-0 text-sm placeholder:text-muted-foreground",
107
113
  style: {
108
114
  minHeight: "48px",
109
115
  maxHeight: "200px",
110
- padding: "12px 48px 12px 16px"
116
+ padding: "12px 12px 12px 16px"
111
117
  },
112
118
  rows: 1
113
119
  }
114
120
  ),
115
- showPauseButton ? /* @__PURE__ */ jsx(
116
- "button",
117
- {
118
- onClick: onPause,
119
- className: "flex items-center justify-center flex-shrink-0 transition-all bg-primary text-primary-foreground hover:bg-primary/90",
120
- style: {
121
- position: "absolute",
122
- bottom: "8px",
123
- right: "8px",
124
- padding: "8px",
125
- borderRadius: "9999px",
126
- width: "32px",
127
- height: "32px"
128
- },
129
- "aria-label": "Pause request",
130
- children: /* @__PURE__ */ jsx(Pause, { className: "w-4 h-4" })
131
- }
132
- ) : /* @__PURE__ */ jsx(
133
- "button",
134
- {
135
- onClick: onSend,
136
- disabled: isInputDisabled || !value.trim(),
137
- className: "flex items-center justify-center flex-shrink-0 transition-all bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",
138
- style: {
139
- position: "absolute",
140
- bottom: "8px",
141
- right: "8px",
142
- padding: "8px",
143
- borderRadius: "9999px",
144
- width: "32px",
145
- height: "32px"
146
- },
147
- "aria-label": "Send message",
148
- children: /* @__PURE__ */ jsx(Send, { className: "w-4 h-4" })
149
- }
150
- )
121
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row items-center gap-1 shrink-0 mb-2 mr-2", children: [
122
+ showVoiceButton && /* @__PURE__ */ jsx(
123
+ "button",
124
+ {
125
+ type: "button",
126
+ onClick: onVoicePress,
127
+ disabled: isVoiceButtonDisabled,
128
+ className: `flex items-center justify-center flex-shrink-0 transition-all rounded-full w-8 h-8 min-w-[32px] ${isRecording ? "animate-pulse" : "hover:opacity-80"} disabled:opacity-50 disabled:cursor-not-allowed`,
129
+ "aria-label": isRecording ? "Stop recording" : "Voice input",
130
+ children: /* @__PURE__ */ jsx(Mic, { className: `w-5 h-5 shrink-0 ${isRecording ? "text-red-500" : "text-foreground"}` })
131
+ }
132
+ ),
133
+ showPauseButton ? /* @__PURE__ */ jsx(
134
+ "button",
135
+ {
136
+ type: "button",
137
+ onClick: onPause,
138
+ className: "flex items-center justify-center flex-shrink-0 transition-all rounded-full w-8 h-8 bg-primary text-primary-foreground hover:bg-primary/90",
139
+ "aria-label": "Pause request",
140
+ children: /* @__PURE__ */ jsx(Pause, { className: "w-4 h-4" })
141
+ }
142
+ ) : /* @__PURE__ */ jsx(
143
+ "button",
144
+ {
145
+ type: "button",
146
+ onClick: onSend,
147
+ disabled: isInputDisabled || !value.trim(),
148
+ className: "flex items-center justify-center flex-shrink-0 transition-all rounded-full w-8 h-8 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",
149
+ "aria-label": "Send message",
150
+ children: /* @__PURE__ */ jsx(Send, { className: "w-4 h-4" })
151
+ }
152
+ )
153
+ ] })
151
154
  ]
152
155
  }
153
156
  ) }) }) });
@@ -748,11 +751,11 @@ function OtpInput({ value, onChange, maxLength, disabled = false }) {
748
751
  textAlign: "center",
749
752
  fontSize: "20px",
750
753
  fontWeight: 600,
751
- border: "1px solid var(--border, #E5E5E5)",
754
+ border: "1px solid hsl(var(--border, 214.3 31.8% 91.4%))",
752
755
  borderRadius: "8px",
753
756
  outline: "none",
754
- backgroundColor: disabled ? "var(--muted, #F5F5F5)" : "transparent",
755
- color: "var(--foreground, #000)",
757
+ backgroundColor: disabled ? "hsl(var(--muted, 210 40% 96.1%))" : "transparent",
758
+ color: "hsl(var(--foreground, 222.2 84% 4.9%))",
756
759
  opacity: disabled ? 0.5 : 1
757
760
  }
758
761
  },
@@ -944,7 +947,7 @@ function UserActionModal({
944
947
  role: "dialog",
945
948
  "aria-modal": "true",
946
949
  style: {
947
- backgroundColor: "var(--card, #FFFFFF)",
950
+ backgroundColor: "hsl(var(--card, 0 0% 100%))",
948
951
  borderRadius: "12px",
949
952
  padding: "24px",
950
953
  width: "100%",
@@ -965,7 +968,7 @@ function UserActionModal({
965
968
  width: "48px",
966
969
  height: "48px",
967
970
  borderRadius: "50%",
968
- backgroundColor: "var(--muted, #F5F5F5)",
971
+ backgroundColor: "hsl(var(--muted, 210 40% 96.1%))",
969
972
  marginBottom: "12px"
970
973
  },
971
974
  children: /* @__PURE__ */ jsx(
@@ -983,7 +986,7 @@ function UserActionModal({
983
986
  style: {
984
987
  fontSize: "18px",
985
988
  fontWeight: 600,
986
- color: "var(--foreground, #000)",
989
+ color: "hsl(var(--foreground, 222.2 84% 4.9%))",
987
990
  margin: 0
988
991
  },
989
992
  children: MODAL_CONTENT.TITLE
@@ -995,7 +998,7 @@ function UserActionModal({
995
998
  {
996
999
  style: {
997
1000
  fontSize: "14px",
998
- color: "var(--muted-foreground, #666)",
1001
+ color: "hsl(var(--muted-foreground, 215.4 16.3% 46.9%))",
999
1002
  textAlign: "center",
1000
1003
  margin: "0 0 24px 0",
1001
1004
  lineHeight: 1.5
@@ -1128,6 +1131,7 @@ function PaymanChat({
1128
1131
  children
1129
1132
  }) {
1130
1133
  const [inputValue, setInputValue] = useState("");
1134
+ const prevInputValueRef = useRef(inputValue);
1131
1135
  const chat = useChat(config, callbacks);
1132
1136
  const {
1133
1137
  messages,
@@ -1144,6 +1148,29 @@ function PaymanChat({
1144
1148
  const rejectUserAction = chat.rejectUserAction ?? NOOP_ASYNC;
1145
1149
  const resendOtp = chat.resendOtp ?? NOOP_ASYNC;
1146
1150
  const isUserActionSupported = typeof chat.approveUserAction === "function" && typeof chat.rejectUserAction === "function" && typeof chat.resendOtp === "function";
1151
+ const {
1152
+ isAvailable: voiceAvailable,
1153
+ isRecording,
1154
+ startRecording,
1155
+ stopRecording,
1156
+ clearTranscript
1157
+ } = useVoice(
1158
+ {
1159
+ lang: config.voiceLang || "en-US",
1160
+ interimResults: config.voiceInterimResults !== false,
1161
+ continuous: config.voiceContinuous !== false
1162
+ },
1163
+ {
1164
+ onResult: (text) => {
1165
+ setInputValue(text);
1166
+ },
1167
+ onEnd: () => {
1168
+ },
1169
+ onError: (error) => {
1170
+ console.error("Voice error:", error);
1171
+ }
1172
+ }
1173
+ );
1147
1174
  const contextValue = useMemo(
1148
1175
  () => ({
1149
1176
  resetSession,
@@ -1190,7 +1217,21 @@ function PaymanChat({
1190
1217
  if (!sessionParams) return false;
1191
1218
  return !!(sessionParams.id?.trim() && sessionParams.name?.trim());
1192
1219
  }, [sessionParams?.id, sessionParams?.name]);
1220
+ useEffect(() => {
1221
+ const wasEmpty = prevInputValueRef.current.trim() === "";
1222
+ const isEmpty = inputValue.trim() === "";
1223
+ prevInputValueRef.current = inputValue;
1224
+ if (!wasEmpty && isEmpty) {
1225
+ clearTranscript();
1226
+ if (isRecording) {
1227
+ stopRecording();
1228
+ }
1229
+ }
1230
+ }, [inputValue, clearTranscript, isRecording, stopRecording]);
1193
1231
  const handleSend = () => {
1232
+ if (isRecording) {
1233
+ stopRecording();
1234
+ }
1194
1235
  if (inputValue.trim() && !disableInput && isSessionParamsConfigured) {
1195
1236
  sendMessage(inputValue.trim());
1196
1237
  setInputValue("");
@@ -1270,10 +1311,14 @@ function PaymanChat({
1270
1311
  onSend: handleSend,
1271
1312
  onPause: cancelStream,
1272
1313
  disabled: isInputDisabled,
1273
- placeholder,
1314
+ placeholder: isRecording ? "Listening..." : placeholder,
1274
1315
  isWaitingForResponse,
1275
1316
  hasSelectedSession: true,
1276
1317
  isSessionParamsConfigured,
1318
+ enableVoice: config.enableVoice === true,
1319
+ onVoicePress: isRecording ? stopRecording : startRecording,
1320
+ voiceAvailable: config.enableVoice === true && voiceAvailable,
1321
+ isRecording,
1277
1322
  inputStyle,
1278
1323
  layout
1279
1324
  }