@paymanai/payman-ask-sdk 1.2.11 → 1.2.13

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,6 +1,6 @@
1
1
  import { useChat, useVoice } from '@paymanai/payman-typescript-ask-sdk';
2
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';
3
+ import { createContext, useContext, useState, useRef, useMemo, useEffect, useCallback, useLayoutEffect } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { motion, AnimatePresence } from 'framer-motion';
@@ -56,6 +56,7 @@ function ChatInput({
56
56
  value,
57
57
  onChange,
58
58
  onSend,
59
+ onPause,
59
60
  disabled = false,
60
61
  placeholder = "Type your message...",
61
62
  isWaitingForResponse = false,
@@ -87,6 +88,7 @@ function ChatInput({
87
88
  }
88
89
  };
89
90
  const isInputDisabled = disabled || isWaitingForResponse;
91
+ const showPauseButton = isWaitingForResponse && onPause;
90
92
  const showVoiceButton = enableVoice && onVoicePress != null;
91
93
  const isVoiceButtonDisabled = isWaitingForResponse || !voiceAvailable || !isSessionParamsConfigured;
92
94
  const canSend = !isInputDisabled && !!value.trim();
@@ -167,7 +169,22 @@ function ChatInput({
167
169
  ]
168
170
  }
169
171
  ),
170
- /* @__PURE__ */ jsx(
172
+ showPauseButton ? /* @__PURE__ */ jsx(
173
+ "button",
174
+ {
175
+ type: "button",
176
+ onClick: onPause,
177
+ className: cn(
178
+ "flex items-center justify-center",
179
+ "w-8 h-8 rounded-full",
180
+ "payman-chat-input-btn-pause",
181
+ "hover:opacity-90 active:scale-95",
182
+ "transition-all duration-150"
183
+ ),
184
+ "aria-label": "Stop response",
185
+ children: /* @__PURE__ */ jsx(Square, { className: "w-3.5 h-3.5", fill: "currentColor" })
186
+ }
187
+ ) : /* @__PURE__ */ jsx(
171
188
  "button",
172
189
  {
173
190
  type: "button",
@@ -191,6 +208,48 @@ function ChatInput({
191
208
  }
192
209
  );
193
210
  }
211
+
212
+ // src/utils/errorMessages.ts
213
+ var WORKFLOW_FAILED = "WORKFLOW_FAILED";
214
+ var STREAM_NOT_STARTED = "STREAM_NOT_STARTED";
215
+ var HTTP_ERROR_PREFIX = /^HTTP\s+(\d+)\s*:\s*([\s\S]+)$/;
216
+ function isFriendlyWorkflowError(errorDetails) {
217
+ if (!errorDetails) return false;
218
+ return errorDetails === WORKFLOW_FAILED || errorDetails === STREAM_NOT_STARTED || errorDetails.includes(WORKFLOW_FAILED);
219
+ }
220
+ function parseErrorPayload(payload) {
221
+ try {
222
+ const parsed = JSON.parse(payload);
223
+ if (typeof parsed === "string") {
224
+ return { message: parsed.trim() || void 0 };
225
+ }
226
+ if (typeof parsed === "object" && parsed !== null) {
227
+ const record = parsed;
228
+ return {
229
+ status: typeof record.status === "number" ? record.status : void 0,
230
+ message: typeof record.message === "string" && record.message.trim() ? record.message.trim() : void 0
231
+ };
232
+ }
233
+ } catch {
234
+ }
235
+ return {};
236
+ }
237
+ function getConflictErrorMessage(errorDetails) {
238
+ if (!errorDetails) return void 0;
239
+ const trimmedError = errorDetails.trim();
240
+ const httpMatch = trimmedError.match(HTTP_ERROR_PREFIX);
241
+ const httpStatus = httpMatch ? Number(httpMatch[1]) : void 0;
242
+ const rawPayload = (httpMatch ? httpMatch[2] : trimmedError).trim();
243
+ const payload = parseErrorPayload(rawPayload);
244
+ const status = payload.status ?? httpStatus;
245
+ if (status !== 409) {
246
+ return void 0;
247
+ }
248
+ if (payload.message) {
249
+ return payload.message;
250
+ }
251
+ return rawPayload || void 0;
252
+ }
194
253
  function ThinkingBlock({ text }) {
195
254
  const [isOpen, setIsOpen] = useState(false);
196
255
  const hasContent = typeof text === "string" && text.trim().length > 0;
@@ -232,12 +291,6 @@ function ThinkingBlock({ text }) {
232
291
  ] });
233
292
  }
234
293
  var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
235
- var WORKFLOW_FAILED = "WORKFLOW_FAILED";
236
- var STREAM_NOT_STARTED = "STREAM_NOT_STARTED";
237
- function isFriendlyError(errorDetails) {
238
- if (!errorDetails) return false;
239
- return errorDetails === WORKFLOW_FAILED || errorDetails === STREAM_NOT_STARTED || errorDetails.includes(WORKFLOW_FAILED);
240
- }
241
294
  function looksLikeRawError(text) {
242
295
  if (!text || text.length < 10) return false;
243
296
  return text.includes("errorType=") || /failed:\s*\{/.test(text);
@@ -277,7 +330,8 @@ function AgentMessage({
277
330
  const content = rawContent.replace(/\\n/g, "\n");
278
331
  const hasMeaningfulContent = content.length > 0 && !looksLikeRawError(content);
279
332
  const completedWithNoContent = !isStreaming && !isCancelled && content.length === 0 && (message.streamProgress === "completed" || message.streamProgress === "error");
280
- const isError = (isFriendlyError(message.errorDetails) || looksLikeRawError(content)) && !hasMeaningfulContent || completedWithNoContent;
333
+ const conflictErrorMessage = getConflictErrorMessage(message.errorDetails);
334
+ const isError = !!conflictErrorMessage || (isFriendlyWorkflowError(message.errorDetails) || looksLikeRawError(content)) && !hasMeaningfulContent || completedWithNoContent;
281
335
  const activeThinkingText = message.activeThinkingText;
282
336
  const allThinkingText = message.allThinkingText;
283
337
  const currentStep = useMemo(
@@ -474,7 +528,7 @@ function AgentMessage({
474
528
  {
475
529
  remarkPlugins: [remarkGfm],
476
530
  components: markdownComponents(),
477
- children: isError ? FRIENDLY_ERROR_MESSAGE : content || (isStreaming ? "Thinking..." : isCancelled ? "Request was stopped." : "")
531
+ children: isError ? conflictErrorMessage ?? FRIENDLY_ERROR_MESSAGE : content || (isStreaming ? "Thinking..." : isCancelled ? "Request was stopped." : "")
478
532
  }
479
533
  )
480
534
  }
@@ -711,6 +765,7 @@ function MessageRowSkeleton({
711
765
  );
712
766
  }
713
767
  var SCROLL_THRESHOLD = 150;
768
+ var LOAD_MORE_THRESHOLD = 80;
714
769
  function MessageList({
715
770
  messages,
716
771
  isLoading = false,
@@ -730,12 +785,17 @@ function MessageList({
730
785
  streamingStepsText,
731
786
  completedStepsText,
732
787
  onExecutionTraceClick,
733
- className
788
+ className,
789
+ onLoadMoreMessages,
790
+ isLoadingMoreMessages = false,
791
+ hasMoreMessages = false
734
792
  }) {
735
793
  const scrollRef = useRef(null);
736
794
  const isNearBottomRef = useRef(true);
737
795
  const [showScrollBtn, setShowScrollBtn] = useState(false);
738
796
  const prevMessageCountRef = useRef(messages.length);
797
+ const scrollHeightBeforePrependRef = useRef(null);
798
+ const firstMessageIdRef = useRef(messages[0]?.id);
739
799
  const getDistanceFromBottom = useCallback(() => {
740
800
  const el = scrollRef.current;
741
801
  if (!el) return 0;
@@ -747,11 +807,27 @@ function MessageList({
747
807
  el.scrollTo({ top: el.scrollHeight, behavior });
748
808
  }, []);
749
809
  const handleScroll = useCallback(() => {
810
+ const el = scrollRef.current;
811
+ if (!el) return;
750
812
  const distance = getDistanceFromBottom();
751
813
  const nearBottom = distance <= SCROLL_THRESHOLD;
752
814
  isNearBottomRef.current = nearBottom;
753
815
  setShowScrollBtn(!nearBottom);
754
- }, [getDistanceFromBottom]);
816
+ if (el.scrollTop <= LOAD_MORE_THRESHOLD && hasMoreMessages && !isLoadingMoreMessages && onLoadMoreMessages) {
817
+ scrollHeightBeforePrependRef.current = el.scrollHeight;
818
+ onLoadMoreMessages();
819
+ }
820
+ }, [getDistanceFromBottom, hasMoreMessages, isLoadingMoreMessages, onLoadMoreMessages]);
821
+ useLayoutEffect(() => {
822
+ const el = scrollRef.current;
823
+ const prevHeight = scrollHeightBeforePrependRef.current;
824
+ const newFirstId = messages[0]?.id;
825
+ if (el && prevHeight !== null && newFirstId !== firstMessageIdRef.current) {
826
+ el.scrollTop = el.scrollHeight - prevHeight;
827
+ scrollHeightBeforePrependRef.current = null;
828
+ }
829
+ firstMessageIdRef.current = newFirstId;
830
+ }, [messages]);
755
831
  useEffect(() => {
756
832
  const prevCount = prevMessageCountRef.current;
757
833
  prevMessageCountRef.current = messages.length;
@@ -862,34 +938,37 @@ function MessageList({
862
938
  className
863
939
  ),
864
940
  children: [
865
- /* @__PURE__ */ jsx(
941
+ /* @__PURE__ */ jsxs(
866
942
  "div",
867
943
  {
868
944
  className: cn(
869
945
  "space-y-4",
870
946
  layout === "centered" ? "max-w-2xl mx-auto px-4 py-6" : "p-4"
871
947
  ),
872
- children: messages.map((message) => /* @__PURE__ */ jsx(
873
- MessageRow,
874
- {
875
- message,
876
- stage,
877
- animated,
878
- showAgentName,
879
- agentName,
880
- showAvatars,
881
- showUserAvatar,
882
- showAssistantAvatar,
883
- layout,
884
- showTimestamps,
885
- showExecutionSteps,
886
- showStreamingDot,
887
- streamingStepsText,
888
- completedStepsText,
889
- onExecutionTraceClick
890
- },
891
- message.id
892
- ))
948
+ children: [
949
+ isLoadingMoreMessages && /* @__PURE__ */ jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsx("div", { className: "w-5 h-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground animate-spin" }) }),
950
+ messages.map((message) => /* @__PURE__ */ jsx(
951
+ MessageRow,
952
+ {
953
+ message,
954
+ stage,
955
+ animated,
956
+ showAgentName,
957
+ agentName,
958
+ showAvatars,
959
+ showUserAvatar,
960
+ showAssistantAvatar,
961
+ layout,
962
+ showTimestamps,
963
+ showExecutionSteps,
964
+ showStreamingDot,
965
+ streamingStepsText,
966
+ completedStepsText,
967
+ onExecutionTraceClick
968
+ },
969
+ message.id
970
+ ))
971
+ ]
893
972
  }
894
973
  ),
895
974
  showScrollBtn && /* @__PURE__ */ jsx("div", { className: "sticky bottom-0 z-20 flex justify-center pb-3 -mt-11", children: /* @__PURE__ */ jsx(
@@ -1369,7 +1448,10 @@ function PaymanChat({
1369
1448
  callbacks = {},
1370
1449
  className,
1371
1450
  style,
1372
- children
1451
+ children,
1452
+ onLoadMoreMessages,
1453
+ isLoadingMoreMessages = false,
1454
+ hasMoreMessages = false
1373
1455
  }) {
1374
1456
  const [inputValue, setInputValue] = useState("");
1375
1457
  const prevInputValueRef = useRef(inputValue);
@@ -1380,6 +1462,7 @@ function PaymanChat({
1380
1462
  isWaitingForResponse,
1381
1463
  resetSession,
1382
1464
  clearMessages,
1465
+ prependMessages,
1383
1466
  cancelStream,
1384
1467
  getSessionId,
1385
1468
  getMessages
@@ -1416,6 +1499,7 @@ function PaymanChat({
1416
1499
  () => ({
1417
1500
  resetSession,
1418
1501
  clearMessages,
1502
+ prependMessages,
1419
1503
  cancelStream,
1420
1504
  getSessionId,
1421
1505
  getMessages,
@@ -1424,6 +1508,7 @@ function PaymanChat({
1424
1508
  [
1425
1509
  resetSession,
1426
1510
  clearMessages,
1511
+ prependMessages,
1427
1512
  cancelStream,
1428
1513
  getSessionId,
1429
1514
  getMessages,
@@ -1541,7 +1626,10 @@ function PaymanChat({
1541
1626
  showStreamingDot,
1542
1627
  streamingStepsText,
1543
1628
  completedStepsText,
1544
- onExecutionTraceClick
1629
+ onExecutionTraceClick,
1630
+ onLoadMoreMessages,
1631
+ isLoadingMoreMessages,
1632
+ hasMoreMessages
1545
1633
  }
1546
1634
  ),
1547
1635
  hasAskPermission && /* @__PURE__ */ jsx(
@@ -1550,6 +1638,7 @@ function PaymanChat({
1550
1638
  value: inputValue,
1551
1639
  onChange: setInputValue,
1552
1640
  onSend: handleSend,
1641
+ onPause: cancelStream,
1553
1642
  disabled: isInputDisabled,
1554
1643
  placeholder: isRecording ? "Listening..." : placeholder,
1555
1644
  isWaitingForResponse,