@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/README.md CHANGED
@@ -41,6 +41,7 @@ function MyApp() {
41
41
  streamEndpoint: "/api/playground/ask/stream", // Important!
42
42
  },
43
43
  workflowName: "my-workflow",
44
+ userId: "user-123", // Required for chatStore and activeStreamStore
44
45
  workflowVersion: 1,
45
46
  stage: "DEV",
46
47
  sessionParams: {
@@ -66,6 +67,7 @@ function MyApp() {
66
67
  authToken: "your-api-key", // API key with stage configured
67
68
  },
68
69
  workflowName: "my-workflow",
70
+ userId: "user-123", // Required for chatStore and activeStreamStore
69
71
  stage: "PROD",
70
72
  sessionParams: {
71
73
  id: "user-123",
@@ -109,6 +111,7 @@ function MyApp() {
109
111
  |----------|------|----------|-------------|
110
112
  | `api` | `APIConfig` | ✅ | API configuration |
111
113
  | `workflowName` | `string` | ✅ | Workflow name |
114
+ | `userId` | `string` | ✅ | User ID for chatStore and activeStreamStore (context store messages management) |
112
115
  | `workflowVersion` | `number` | | Workflow version (default: 1) |
113
116
  | `stage` | `WorkflowStage` | | Stage (DEV/SANDBOX/PROD) |
114
117
  | `sessionParams` | `SessionParams` | | User session info |
package/dist/index.d.mts CHANGED
@@ -31,7 +31,7 @@ type MessageDisplay = {
31
31
  content: string;
32
32
  timestamp: string;
33
33
  isError?: boolean;
34
- /** When set to "WORKFLOW_FAILED" or "STREAM_NOT_STARTED", the UI shows the friendly "Oops, something went wrong" message. Other errors (e.g. subintent failures) do not show it. */
34
+ /** "WORKFLOW_FAILED" and "STREAM_NOT_STARTED" show the friendly fallback; HTTP 409 conflicts render the backend message from the error payload. */
35
35
  errorDetails?: string;
36
36
  chunks?: ChunkDisplay[];
37
37
  tracingData?: unknown;
@@ -71,6 +71,8 @@ type ChatConfig = {
71
71
  api: APIConfig;
72
72
  /** Workflow name */
73
73
  workflowName: string;
74
+ /** User ID for chatStore and activeStreamStore — required for context store messages management */
75
+ userId?: string;
74
76
  /** Workflow version */
75
77
  workflowVersion?: number;
76
78
  /** Stage/Environment */
@@ -163,6 +165,12 @@ type PaymanChatProps = {
163
165
  style?: React__default.CSSProperties;
164
166
  /** Custom children to render (e.g., header) */
165
167
  children?: React__default.ReactNode;
168
+ /** Called when the user scrolls to the top — use to load older messages */
169
+ onLoadMoreMessages?: () => void;
170
+ /** Show a loading spinner at the top of the list while fetching older messages */
171
+ isLoadingMoreMessages?: boolean;
172
+ /** Whether there are more messages to load (hides the spinner/trigger when false) */
173
+ hasMoreMessages?: boolean;
166
174
  };
167
175
  type ChatInputProps = {
168
176
  /** Input value */
@@ -171,6 +179,8 @@ type ChatInputProps = {
171
179
  onChange: (value: string) => void;
172
180
  /** On send handler */
173
181
  onSend: () => void;
182
+ /** On pause/cancel handler */
183
+ onPause?: () => void;
174
184
  /** Disabled state */
175
185
  disabled?: boolean;
176
186
  /** Placeholder text */
@@ -249,6 +259,12 @@ type MessageListProps = {
249
259
  }) => void;
250
260
  /** Custom class name */
251
261
  className?: string;
262
+ /** Called when the user scrolls to the top — use to load older messages */
263
+ onLoadMoreMessages?: () => void;
264
+ /** Show a loading spinner at the top while fetching older messages */
265
+ isLoadingMoreMessages?: boolean;
266
+ /** Whether there are more messages to load */
267
+ hasMoreMessages?: boolean;
252
268
  };
253
269
  type MessageRowProps = {
254
270
  /** Message to display */
@@ -337,7 +353,7 @@ type StreamingMessageProps = {
337
353
  currentMessage?: string;
338
354
  /** Stream progress */
339
355
  streamProgress: StreamProgress;
340
- /** When set to "WORKFLOW_FAILED" or "STREAM_NOT_STARTED", the UI shows the friendly error message. Other errors do not. */
356
+ /** "WORKFLOW_FAILED" and "STREAM_NOT_STARTED" show the friendly fallback; HTTP 409 conflicts render the backend message from the error payload. */
341
357
  error?: string;
342
358
  /** Streaming steps */
343
359
  steps?: StreamingStep[];
@@ -393,7 +409,7 @@ type UserActionModalProps = {
393
409
  clearOtpTrigger: number;
394
410
  };
395
411
 
396
- declare function PaymanChat({ config, callbacks, className, style, children, }: PaymanChatProps): react_jsx_runtime.JSX.Element;
412
+ declare function PaymanChat({ config, callbacks, className, style, children, onLoadMoreMessages, isLoadingMoreMessages, hasMoreMessages, }: PaymanChatProps): react_jsx_runtime.JSX.Element;
397
413
 
398
414
  interface PaymanChatContextValue {
399
415
  /**
@@ -404,6 +420,10 @@ interface PaymanChatContextValue {
404
420
  * Clear all messages without resetting session ID
405
421
  */
406
422
  clearMessages: () => void;
423
+ /**
424
+ * Prepend older messages to the top of the list (e.g. from history pagination)
425
+ */
426
+ prependMessages: (messages: MessageDisplay[]) => void;
407
427
  /**
408
428
  * Cancel current streaming operation
409
429
  */
package/dist/index.d.ts CHANGED
@@ -31,7 +31,7 @@ type MessageDisplay = {
31
31
  content: string;
32
32
  timestamp: string;
33
33
  isError?: boolean;
34
- /** When set to "WORKFLOW_FAILED" or "STREAM_NOT_STARTED", the UI shows the friendly "Oops, something went wrong" message. Other errors (e.g. subintent failures) do not show it. */
34
+ /** "WORKFLOW_FAILED" and "STREAM_NOT_STARTED" show the friendly fallback; HTTP 409 conflicts render the backend message from the error payload. */
35
35
  errorDetails?: string;
36
36
  chunks?: ChunkDisplay[];
37
37
  tracingData?: unknown;
@@ -71,6 +71,8 @@ type ChatConfig = {
71
71
  api: APIConfig;
72
72
  /** Workflow name */
73
73
  workflowName: string;
74
+ /** User ID for chatStore and activeStreamStore — required for context store messages management */
75
+ userId?: string;
74
76
  /** Workflow version */
75
77
  workflowVersion?: number;
76
78
  /** Stage/Environment */
@@ -163,6 +165,12 @@ type PaymanChatProps = {
163
165
  style?: React__default.CSSProperties;
164
166
  /** Custom children to render (e.g., header) */
165
167
  children?: React__default.ReactNode;
168
+ /** Called when the user scrolls to the top — use to load older messages */
169
+ onLoadMoreMessages?: () => void;
170
+ /** Show a loading spinner at the top of the list while fetching older messages */
171
+ isLoadingMoreMessages?: boolean;
172
+ /** Whether there are more messages to load (hides the spinner/trigger when false) */
173
+ hasMoreMessages?: boolean;
166
174
  };
167
175
  type ChatInputProps = {
168
176
  /** Input value */
@@ -171,6 +179,8 @@ type ChatInputProps = {
171
179
  onChange: (value: string) => void;
172
180
  /** On send handler */
173
181
  onSend: () => void;
182
+ /** On pause/cancel handler */
183
+ onPause?: () => void;
174
184
  /** Disabled state */
175
185
  disabled?: boolean;
176
186
  /** Placeholder text */
@@ -249,6 +259,12 @@ type MessageListProps = {
249
259
  }) => void;
250
260
  /** Custom class name */
251
261
  className?: string;
262
+ /** Called when the user scrolls to the top — use to load older messages */
263
+ onLoadMoreMessages?: () => void;
264
+ /** Show a loading spinner at the top while fetching older messages */
265
+ isLoadingMoreMessages?: boolean;
266
+ /** Whether there are more messages to load */
267
+ hasMoreMessages?: boolean;
252
268
  };
253
269
  type MessageRowProps = {
254
270
  /** Message to display */
@@ -337,7 +353,7 @@ type StreamingMessageProps = {
337
353
  currentMessage?: string;
338
354
  /** Stream progress */
339
355
  streamProgress: StreamProgress;
340
- /** When set to "WORKFLOW_FAILED" or "STREAM_NOT_STARTED", the UI shows the friendly error message. Other errors do not. */
356
+ /** "WORKFLOW_FAILED" and "STREAM_NOT_STARTED" show the friendly fallback; HTTP 409 conflicts render the backend message from the error payload. */
341
357
  error?: string;
342
358
  /** Streaming steps */
343
359
  steps?: StreamingStep[];
@@ -393,7 +409,7 @@ type UserActionModalProps = {
393
409
  clearOtpTrigger: number;
394
410
  };
395
411
 
396
- declare function PaymanChat({ config, callbacks, className, style, children, }: PaymanChatProps): react_jsx_runtime.JSX.Element;
412
+ declare function PaymanChat({ config, callbacks, className, style, children, onLoadMoreMessages, isLoadingMoreMessages, hasMoreMessages, }: PaymanChatProps): react_jsx_runtime.JSX.Element;
397
413
 
398
414
  interface PaymanChatContextValue {
399
415
  /**
@@ -404,6 +420,10 @@ interface PaymanChatContextValue {
404
420
  * Clear all messages without resetting session ID
405
421
  */
406
422
  clearMessages: () => void;
423
+ /**
424
+ * Prepend older messages to the top of the list (e.g. from history pagination)
425
+ */
426
+ prependMessages: (messages: MessageDisplay[]) => void;
407
427
  /**
408
428
  * Cancel current streaming operation
409
429
  */
package/dist/index.js CHANGED
@@ -62,6 +62,7 @@ function ChatInput({
62
62
  value,
63
63
  onChange,
64
64
  onSend,
65
+ onPause,
65
66
  disabled = false,
66
67
  placeholder = "Type your message...",
67
68
  isWaitingForResponse = false,
@@ -93,6 +94,7 @@ function ChatInput({
93
94
  }
94
95
  };
95
96
  const isInputDisabled = disabled || isWaitingForResponse;
97
+ const showPauseButton = isWaitingForResponse && onPause;
96
98
  const showVoiceButton = enableVoice && onVoicePress != null;
97
99
  const isVoiceButtonDisabled = isWaitingForResponse || !voiceAvailable || !isSessionParamsConfigured;
98
100
  const canSend = !isInputDisabled && !!value.trim();
@@ -173,7 +175,22 @@ function ChatInput({
173
175
  ]
174
176
  }
175
177
  ),
176
- /* @__PURE__ */ jsxRuntime.jsx(
178
+ showPauseButton ? /* @__PURE__ */ jsxRuntime.jsx(
179
+ "button",
180
+ {
181
+ type: "button",
182
+ onClick: onPause,
183
+ className: cn(
184
+ "flex items-center justify-center",
185
+ "w-8 h-8 rounded-full",
186
+ "payman-chat-input-btn-pause",
187
+ "hover:opacity-90 active:scale-95",
188
+ "transition-all duration-150"
189
+ ),
190
+ "aria-label": "Stop response",
191
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { className: "w-3.5 h-3.5", fill: "currentColor" })
192
+ }
193
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
177
194
  "button",
178
195
  {
179
196
  type: "button",
@@ -197,6 +214,48 @@ function ChatInput({
197
214
  }
198
215
  );
199
216
  }
217
+
218
+ // src/utils/errorMessages.ts
219
+ var WORKFLOW_FAILED = "WORKFLOW_FAILED";
220
+ var STREAM_NOT_STARTED = "STREAM_NOT_STARTED";
221
+ var HTTP_ERROR_PREFIX = /^HTTP\s+(\d+)\s*:\s*([\s\S]+)$/;
222
+ function isFriendlyWorkflowError(errorDetails) {
223
+ if (!errorDetails) return false;
224
+ return errorDetails === WORKFLOW_FAILED || errorDetails === STREAM_NOT_STARTED || errorDetails.includes(WORKFLOW_FAILED);
225
+ }
226
+ function parseErrorPayload(payload) {
227
+ try {
228
+ const parsed = JSON.parse(payload);
229
+ if (typeof parsed === "string") {
230
+ return { message: parsed.trim() || void 0 };
231
+ }
232
+ if (typeof parsed === "object" && parsed !== null) {
233
+ const record = parsed;
234
+ return {
235
+ status: typeof record.status === "number" ? record.status : void 0,
236
+ message: typeof record.message === "string" && record.message.trim() ? record.message.trim() : void 0
237
+ };
238
+ }
239
+ } catch {
240
+ }
241
+ return {};
242
+ }
243
+ function getConflictErrorMessage(errorDetails) {
244
+ if (!errorDetails) return void 0;
245
+ const trimmedError = errorDetails.trim();
246
+ const httpMatch = trimmedError.match(HTTP_ERROR_PREFIX);
247
+ const httpStatus = httpMatch ? Number(httpMatch[1]) : void 0;
248
+ const rawPayload = (httpMatch ? httpMatch[2] : trimmedError).trim();
249
+ const payload = parseErrorPayload(rawPayload);
250
+ const status = payload.status ?? httpStatus;
251
+ if (status !== 409) {
252
+ return void 0;
253
+ }
254
+ if (payload.message) {
255
+ return payload.message;
256
+ }
257
+ return rawPayload || void 0;
258
+ }
200
259
  function ThinkingBlock({ text }) {
201
260
  const [isOpen, setIsOpen] = react.useState(false);
202
261
  const hasContent = typeof text === "string" && text.trim().length > 0;
@@ -238,12 +297,6 @@ function ThinkingBlock({ text }) {
238
297
  ] });
239
298
  }
240
299
  var FRIENDLY_ERROR_MESSAGE = "Oops, something went wrong. Please try again.";
241
- var WORKFLOW_FAILED = "WORKFLOW_FAILED";
242
- var STREAM_NOT_STARTED = "STREAM_NOT_STARTED";
243
- function isFriendlyError(errorDetails) {
244
- if (!errorDetails) return false;
245
- return errorDetails === WORKFLOW_FAILED || errorDetails === STREAM_NOT_STARTED || errorDetails.includes(WORKFLOW_FAILED);
246
- }
247
300
  function looksLikeRawError(text) {
248
301
  if (!text || text.length < 10) return false;
249
302
  return text.includes("errorType=") || /failed:\s*\{/.test(text);
@@ -283,7 +336,8 @@ function AgentMessage({
283
336
  const content = rawContent.replace(/\\n/g, "\n");
284
337
  const hasMeaningfulContent = content.length > 0 && !looksLikeRawError(content);
285
338
  const completedWithNoContent = !isStreaming && !isCancelled && content.length === 0 && (message.streamProgress === "completed" || message.streamProgress === "error");
286
- const isError = (isFriendlyError(message.errorDetails) || looksLikeRawError(content)) && !hasMeaningfulContent || completedWithNoContent;
339
+ const conflictErrorMessage = getConflictErrorMessage(message.errorDetails);
340
+ const isError = !!conflictErrorMessage || (isFriendlyWorkflowError(message.errorDetails) || looksLikeRawError(content)) && !hasMeaningfulContent || completedWithNoContent;
287
341
  const activeThinkingText = message.activeThinkingText;
288
342
  const allThinkingText = message.allThinkingText;
289
343
  const currentStep = react.useMemo(
@@ -480,7 +534,7 @@ function AgentMessage({
480
534
  {
481
535
  remarkPlugins: [remarkGfm__default.default],
482
536
  components: markdownComponents(),
483
- children: isError ? FRIENDLY_ERROR_MESSAGE : content || (isStreaming ? "Thinking..." : isCancelled ? "Request was stopped." : "")
537
+ children: isError ? conflictErrorMessage ?? FRIENDLY_ERROR_MESSAGE : content || (isStreaming ? "Thinking..." : isCancelled ? "Request was stopped." : "")
484
538
  }
485
539
  )
486
540
  }
@@ -717,6 +771,7 @@ function MessageRowSkeleton({
717
771
  );
718
772
  }
719
773
  var SCROLL_THRESHOLD = 150;
774
+ var LOAD_MORE_THRESHOLD = 80;
720
775
  function MessageList({
721
776
  messages,
722
777
  isLoading = false,
@@ -736,12 +791,17 @@ function MessageList({
736
791
  streamingStepsText,
737
792
  completedStepsText,
738
793
  onExecutionTraceClick,
739
- className
794
+ className,
795
+ onLoadMoreMessages,
796
+ isLoadingMoreMessages = false,
797
+ hasMoreMessages = false
740
798
  }) {
741
799
  const scrollRef = react.useRef(null);
742
800
  const isNearBottomRef = react.useRef(true);
743
801
  const [showScrollBtn, setShowScrollBtn] = react.useState(false);
744
802
  const prevMessageCountRef = react.useRef(messages.length);
803
+ const scrollHeightBeforePrependRef = react.useRef(null);
804
+ const firstMessageIdRef = react.useRef(messages[0]?.id);
745
805
  const getDistanceFromBottom = react.useCallback(() => {
746
806
  const el = scrollRef.current;
747
807
  if (!el) return 0;
@@ -753,11 +813,27 @@ function MessageList({
753
813
  el.scrollTo({ top: el.scrollHeight, behavior });
754
814
  }, []);
755
815
  const handleScroll = react.useCallback(() => {
816
+ const el = scrollRef.current;
817
+ if (!el) return;
756
818
  const distance = getDistanceFromBottom();
757
819
  const nearBottom = distance <= SCROLL_THRESHOLD;
758
820
  isNearBottomRef.current = nearBottom;
759
821
  setShowScrollBtn(!nearBottom);
760
- }, [getDistanceFromBottom]);
822
+ if (el.scrollTop <= LOAD_MORE_THRESHOLD && hasMoreMessages && !isLoadingMoreMessages && onLoadMoreMessages) {
823
+ scrollHeightBeforePrependRef.current = el.scrollHeight;
824
+ onLoadMoreMessages();
825
+ }
826
+ }, [getDistanceFromBottom, hasMoreMessages, isLoadingMoreMessages, onLoadMoreMessages]);
827
+ react.useLayoutEffect(() => {
828
+ const el = scrollRef.current;
829
+ const prevHeight = scrollHeightBeforePrependRef.current;
830
+ const newFirstId = messages[0]?.id;
831
+ if (el && prevHeight !== null && newFirstId !== firstMessageIdRef.current) {
832
+ el.scrollTop = el.scrollHeight - prevHeight;
833
+ scrollHeightBeforePrependRef.current = null;
834
+ }
835
+ firstMessageIdRef.current = newFirstId;
836
+ }, [messages]);
761
837
  react.useEffect(() => {
762
838
  const prevCount = prevMessageCountRef.current;
763
839
  prevMessageCountRef.current = messages.length;
@@ -868,34 +944,37 @@ function MessageList({
868
944
  className
869
945
  ),
870
946
  children: [
871
- /* @__PURE__ */ jsxRuntime.jsx(
947
+ /* @__PURE__ */ jsxRuntime.jsxs(
872
948
  "div",
873
949
  {
874
950
  className: cn(
875
951
  "space-y-4",
876
952
  layout === "centered" ? "max-w-2xl mx-auto px-4 py-6" : "p-4"
877
953
  ),
878
- children: messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx(
879
- MessageRow,
880
- {
881
- message,
882
- stage,
883
- animated,
884
- showAgentName,
885
- agentName,
886
- showAvatars,
887
- showUserAvatar,
888
- showAssistantAvatar,
889
- layout,
890
- showTimestamps,
891
- showExecutionSteps,
892
- showStreamingDot,
893
- streamingStepsText,
894
- completedStepsText,
895
- onExecutionTraceClick
896
- },
897
- message.id
898
- ))
954
+ children: [
955
+ isLoadingMoreMessages && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-5 h-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground animate-spin" }) }),
956
+ messages.map((message) => /* @__PURE__ */ jsxRuntime.jsx(
957
+ MessageRow,
958
+ {
959
+ message,
960
+ stage,
961
+ animated,
962
+ showAgentName,
963
+ agentName,
964
+ showAvatars,
965
+ showUserAvatar,
966
+ showAssistantAvatar,
967
+ layout,
968
+ showTimestamps,
969
+ showExecutionSteps,
970
+ showStreamingDot,
971
+ streamingStepsText,
972
+ completedStepsText,
973
+ onExecutionTraceClick
974
+ },
975
+ message.id
976
+ ))
977
+ ]
899
978
  }
900
979
  ),
901
980
  showScrollBtn && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky bottom-0 z-20 flex justify-center pb-3 -mt-11", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -1375,7 +1454,10 @@ function PaymanChat({
1375
1454
  callbacks = {},
1376
1455
  className,
1377
1456
  style,
1378
- children
1457
+ children,
1458
+ onLoadMoreMessages,
1459
+ isLoadingMoreMessages = false,
1460
+ hasMoreMessages = false
1379
1461
  }) {
1380
1462
  const [inputValue, setInputValue] = react.useState("");
1381
1463
  const prevInputValueRef = react.useRef(inputValue);
@@ -1386,6 +1468,7 @@ function PaymanChat({
1386
1468
  isWaitingForResponse,
1387
1469
  resetSession,
1388
1470
  clearMessages,
1471
+ prependMessages,
1389
1472
  cancelStream,
1390
1473
  getSessionId,
1391
1474
  getMessages
@@ -1422,6 +1505,7 @@ function PaymanChat({
1422
1505
  () => ({
1423
1506
  resetSession,
1424
1507
  clearMessages,
1508
+ prependMessages,
1425
1509
  cancelStream,
1426
1510
  getSessionId,
1427
1511
  getMessages,
@@ -1430,6 +1514,7 @@ function PaymanChat({
1430
1514
  [
1431
1515
  resetSession,
1432
1516
  clearMessages,
1517
+ prependMessages,
1433
1518
  cancelStream,
1434
1519
  getSessionId,
1435
1520
  getMessages,
@@ -1547,7 +1632,10 @@ function PaymanChat({
1547
1632
  showStreamingDot,
1548
1633
  streamingStepsText,
1549
1634
  completedStepsText,
1550
- onExecutionTraceClick
1635
+ onExecutionTraceClick,
1636
+ onLoadMoreMessages,
1637
+ isLoadingMoreMessages,
1638
+ hasMoreMessages
1551
1639
  }
1552
1640
  ),
1553
1641
  hasAskPermission && /* @__PURE__ */ jsxRuntime.jsx(
@@ -1556,6 +1644,7 @@ function PaymanChat({
1556
1644
  value: inputValue,
1557
1645
  onChange: setInputValue,
1558
1646
  onSend: handleSend,
1647
+ onPause: cancelStream,
1559
1648
  disabled: isInputDisabled,
1560
1649
  placeholder: isRecording ? "Listening..." : placeholder,
1561
1650
  isWaitingForResponse,