@paymanai/payman-ask-sdk 4.0.21 → 4.0.23

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,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  import { createContext, forwardRef, useCallback, useRef, useState, useMemo, useEffect, useImperativeHandle, useLayoutEffect, useContext } from 'react';
2
3
  import { AnimatePresence, motion } from 'framer-motion';
3
4
  import { clsx } from 'clsx';
@@ -709,8 +710,7 @@ function buildRequestBody(config, userMessage, sessionId, options) {
709
710
  sessionAttributes,
710
711
  analysisMode: options?.analysisMode,
711
712
  locale: resolveLocale(config.locale),
712
- timezone: resolveTimezone(config.timezone),
713
- ...options?.attachments?.length ? { attachments: options.attachments } : {}
713
+ timezone: resolveTimezone(config.timezone)
714
714
  };
715
715
  }
716
716
  function resolveLocale(configured) {
@@ -755,7 +755,6 @@ function buildResolveImagesUrl(config) {
755
755
  const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
756
756
  return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
757
757
  }
758
- var NGROK_SKIP_BROWSER_WARNING = "ngrok-skip-browser-warning";
759
758
  function buildRequestHeaders(config) {
760
759
  const headers = {
761
760
  ...config.api.headers
@@ -763,9 +762,6 @@ function buildRequestHeaders(config) {
763
762
  if (config.api.authToken) {
764
763
  headers.Authorization = `Bearer ${config.api.authToken}`;
765
764
  }
766
- if (!headers[NGROK_SKIP_BROWSER_WARNING]) {
767
- headers[NGROK_SKIP_BROWSER_WARNING] = "true";
768
- }
769
765
  return headers;
770
766
  }
771
767
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
@@ -1046,81 +1042,6 @@ function createCancelledMessageUpdate(steps, currentMessage) {
1046
1042
  currentMessage: currentMessage || "Thinking..."
1047
1043
  };
1048
1044
  }
1049
- var DEFAULT_SIGNED_URL_ENDPOINT = "/api/files/signed-url";
1050
- function fileExtension(filename) {
1051
- const ext = filename.split(".").pop()?.toLowerCase().replace(/^\./, "");
1052
- return ext && ext.length > 0 ? ext : "bin";
1053
- }
1054
- function resolveMimeType(file) {
1055
- if (file.type && file.type.trim().length > 0) return file.type;
1056
- const ext = fileExtension(file.name);
1057
- const byExt = {
1058
- pdf: "application/pdf",
1059
- png: "image/png",
1060
- jpg: "image/jpeg",
1061
- jpeg: "image/jpeg",
1062
- gif: "image/gif",
1063
- webp: "image/webp"
1064
- };
1065
- return byExt[ext] ?? "application/octet-stream";
1066
- }
1067
- function buildSignedUrlEndpoint(config, ext) {
1068
- const endpoint = config.api.signedUrlEndpoint || DEFAULT_SIGNED_URL_ENDPOINT;
1069
- const queryParams = new URLSearchParams({ extn: ext.replace(/^\./, "") });
1070
- return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
1071
- }
1072
- async function requestSignedUrl(config, ext, signal) {
1073
- const url = buildSignedUrlEndpoint(config, ext);
1074
- const headers = buildRequestHeaders(config);
1075
- const response = await fetch(url, {
1076
- method: "GET",
1077
- headers,
1078
- signal
1079
- });
1080
- if (!response.ok) {
1081
- const errorText = await response.text().catch(() => "");
1082
- throw new Error(
1083
- errorText ? `Failed to get upload URL (${response.status}): ${errorText}` : `Failed to get upload URL (${response.status})`
1084
- );
1085
- }
1086
- const data = await response.json();
1087
- if (!data.key || !data.url) {
1088
- throw new Error("Signed URL response missing key or url");
1089
- }
1090
- return { key: data.key, url: data.url };
1091
- }
1092
- async function uploadToSignedUrl(signedUrl, file, mimeType, signal) {
1093
- const response = await fetch(signedUrl, {
1094
- method: "PUT",
1095
- headers: {
1096
- "Content-Type": mimeType,
1097
- "x-ms-blob-type": "BlockBlob"
1098
- },
1099
- body: file,
1100
- signal
1101
- });
1102
- if (!response.ok) {
1103
- const errorText = await response.text().catch(() => "");
1104
- throw new Error(
1105
- errorText ? `Failed to upload file (${response.status}): ${errorText}` : `Failed to upload file (${response.status})`
1106
- );
1107
- }
1108
- }
1109
- async function uploadAttachment(config, file, signal) {
1110
- const ext = fileExtension(file.name);
1111
- const mimeType = resolveMimeType(file);
1112
- const { key, url } = await requestSignedUrl(config, ext, signal);
1113
- await uploadToSignedUrl(url, file, mimeType, signal);
1114
- return {
1115
- tempKey: key,
1116
- filename: file.name,
1117
- mimeType
1118
- };
1119
- }
1120
- async function uploadAttachments(config, files, signal) {
1121
- const uploads = files.map((file) => uploadAttachment(config, file, signal));
1122
- return Promise.all(uploads);
1123
- }
1124
1045
  var UserActionStaleError = class extends Error {
1125
1046
  constructor(userActionId, message = "User action is no longer actionable") {
1126
1047
  super(message);
@@ -1157,6 +1078,9 @@ async function cancelUserAction(config, userActionId) {
1157
1078
  async function resendUserAction(config, userActionId) {
1158
1079
  return sendUserActionRequest(config, userActionId, "resend");
1159
1080
  }
1081
+ async function expireUserAction(config, userActionId) {
1082
+ return sendUserActionRequest(config, userActionId, "expired");
1083
+ }
1160
1084
  var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1161
1085
  function upsertPrompt(prompts, req) {
1162
1086
  const active = { ...req, status: "pending" };
@@ -1185,32 +1109,12 @@ function getStoredOrInitialMessages(config) {
1185
1109
  function getSessionIdFromMessages(messages) {
1186
1110
  return messages.find((message) => message.sessionId)?.sessionId;
1187
1111
  }
1188
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "gif", "webp", "avif"]);
1189
- function attachmentKindFromFile(file) {
1190
- const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
1191
- if (IMAGE_EXTENSIONS.has(ext) || file.type.startsWith("image/")) return "image";
1192
- return "file";
1193
- }
1194
- function buildMessageAttachments(files) {
1195
- return files.map((file, index) => {
1196
- const kind = attachmentKindFromFile(file);
1197
- return {
1198
- id: `att-${Date.now()}-${index}`,
1199
- filename: file.name,
1200
- mimeType: file.type || "application/octet-stream",
1201
- // Blob URL for all local files so PDFs/docs stay previewable after send.
1202
- previewUrl: URL.createObjectURL(file),
1203
- kind
1204
- };
1205
- });
1206
- }
1207
1112
  function useChatV2(config, callbacks = {}) {
1208
1113
  const [messages, setMessages] = useState(() => getStoredOrInitialMessages(config));
1209
1114
  const [isWaitingForResponse, setIsWaitingForResponse] = useState(() => {
1210
1115
  if (!config.userId) return false;
1211
1116
  return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1212
1117
  });
1213
- const [isUploadingAttachments, setIsUploadingAttachments] = useState(false);
1214
1118
  const sessionIdRef = useRef(
1215
1119
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1216
1120
  );
@@ -1285,45 +1189,21 @@ function useChatV2(config, callbacks = {}) {
1285
1189
  );
1286
1190
  const sendMessage = useCallback(
1287
1191
  async (userMessage, options) => {
1288
- const trimmedMessage = userMessage.trim();
1289
- const files = options?.files ?? [];
1290
- const hasPreuploadedAttachments = (options?.attachments?.length ?? 0) > 0;
1291
- if (!trimmedMessage && files.length === 0 && !hasPreuploadedAttachments) return;
1292
- let streamAttachments = options?.attachments;
1293
- if (!streamAttachments && files.length > 0) {
1294
- setIsUploadingAttachments(true);
1295
- try {
1296
- streamAttachments = await uploadAttachments(configRef.current, files);
1297
- } catch (error) {
1298
- if (error.name !== "AbortError") {
1299
- callbacksRef.current.onError?.(error);
1300
- }
1301
- throw error;
1302
- } finally {
1303
- setIsUploadingAttachments(false);
1304
- }
1305
- }
1192
+ if (!userMessage.trim()) return;
1306
1193
  if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1307
1194
  sessionIdRef.current = generateId();
1308
1195
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1309
1196
  }
1310
- const messageAttachments = files.length > 0 ? buildMessageAttachments(files) : streamAttachments?.length ? streamAttachments.map((attachment, index) => ({
1311
- id: `att-${Date.now()}-${index}`,
1312
- filename: attachment.filename,
1313
- mimeType: attachment.mimeType,
1314
- kind: attachment.mimeType.startsWith("image/") ? "image" : "file"
1315
- })) : void 0;
1316
1197
  const userMessageId = `user-${Date.now()}`;
1317
1198
  const userMsg = {
1318
1199
  id: userMessageId,
1319
1200
  sessionId: sessionIdRef.current,
1320
1201
  role: "user",
1321
- content: trimmedMessage,
1322
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1323
- attachments: messageAttachments
1202
+ content: userMessage,
1203
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1324
1204
  };
1325
1205
  setMessages((prev) => [...prev, userMsg]);
1326
- callbacksRef.current.onMessageSent?.(trimmedMessage);
1206
+ callbacksRef.current.onMessageSent?.(userMessage);
1327
1207
  setIsWaitingForResponse(true);
1328
1208
  callbacksRef.current.onStreamStart?.();
1329
1209
  const streamingId = `assistant-${Date.now()}`;
@@ -1350,14 +1230,11 @@ function useChatV2(config, callbacks = {}) {
1350
1230
  activeStreamStore.start(userId, abortController, initialMessages);
1351
1231
  }
1352
1232
  const newSessionId = await startStream(
1353
- trimmedMessage,
1233
+ userMessage,
1354
1234
  streamingId,
1355
1235
  sessionIdRef.current,
1356
1236
  abortController,
1357
- {
1358
- analysisMode: options?.analysisMode,
1359
- attachments: streamAttachments
1360
- }
1237
+ options
1361
1238
  );
1362
1239
  const finalStreamUserId = streamUserIdRef.current ?? userId;
1363
1240
  if (finalStreamUserId) {
@@ -1495,6 +1372,19 @@ function useChatV2(config, callbacks = {}) {
1495
1372
  },
1496
1373
  [setPromptStatus]
1497
1374
  );
1375
+ const expireUserAction2 = useCallback(
1376
+ async (userActionId) => {
1377
+ setPromptStatus(userActionId, "expired");
1378
+ try {
1379
+ await expireUserAction(configRef.current, userActionId);
1380
+ } catch (error) {
1381
+ if (error instanceof UserActionStaleError) return;
1382
+ callbacksRef.current.onError?.(error);
1383
+ throw error;
1384
+ }
1385
+ },
1386
+ [setPromptStatus]
1387
+ );
1498
1388
  const dismissNotification = useCallback((id) => {
1499
1389
  setUserActionState((prev) => ({
1500
1390
  ...prev,
@@ -1535,18 +1425,6 @@ function useChatV2(config, callbacks = {}) {
1535
1425
  setMessages(config.initialMessages);
1536
1426
  sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
1537
1427
  }, [config.initialMessages, config.initialSessionId, config.userId]);
1538
- const hydratedSessionIdRef = useRef(void 0);
1539
- useEffect(() => {
1540
- if (config.userId) return;
1541
- if (!config.initialMessages?.length) return;
1542
- const sessionId = config.initialSessionId;
1543
- if (hydratedSessionIdRef.current === sessionId && messagesRef.current.length > 0) {
1544
- return;
1545
- }
1546
- hydratedSessionIdRef.current = sessionId;
1547
- setMessages(config.initialMessages);
1548
- sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? sessionId;
1549
- }, [config.initialMessages, config.initialSessionId, config.userId]);
1550
1428
  useEffect(() => {
1551
1429
  const prevUserId = prevUserIdRef.current;
1552
1430
  prevUserIdRef.current = config.userId;
@@ -1581,12 +1459,12 @@ function useChatV2(config, callbacks = {}) {
1581
1459
  getSessionId,
1582
1460
  getMessages,
1583
1461
  isWaitingForResponse,
1584
- isUploadingAttachments,
1585
1462
  sessionId: sessionIdRef.current,
1586
1463
  userActionState,
1587
1464
  submitUserAction: submitUserAction2,
1588
1465
  cancelUserAction: cancelUserAction2,
1589
1466
  resendUserAction: resendUserAction2,
1467
+ expireUserAction: expireUserAction2,
1590
1468
  dismissNotification
1591
1469
  };
1592
1470
  }
@@ -3501,9 +3379,88 @@ function MarkdownImageV2({
3501
3379
  }
3502
3380
  ) });
3503
3381
  }
3504
- function buildComponents(onImageClick, isResolvingRef) {
3382
+ var RAG_SOURCE_CLASS = "payman-v2-rag-source";
3383
+ var RAG_FOLLOW_UP_CLASS = "payman-v2-rag-follow-up";
3384
+ var SOURCE_PATTERN = /^Sources?:\s*\S/i;
3385
+ var MAX_FOLLOW_UP_LENGTH = 320;
3386
+ var HR_PATTERN = /^-{3,}$|^_{3,}$|^\*{3,}$/;
3387
+ function normalizeRagParagraphText(text) {
3388
+ return text.replace(/\s+/g, " ").trim();
3389
+ }
3390
+ function isRagSourceParagraph(text) {
3391
+ return SOURCE_PATTERN.test(normalizeRagParagraphText(text));
3392
+ }
3393
+ function isRagFollowUpCandidate(text) {
3394
+ const normalized = normalizeRagParagraphText(text);
3395
+ return normalized.length > 0 && normalized.length <= MAX_FOLLOW_UP_LENGTH && normalized.endsWith("?");
3396
+ }
3397
+ function extractRagParagraphTexts(content) {
3398
+ const paragraphs = [];
3399
+ const currentParagraph = [];
3400
+ const flushParagraph = () => {
3401
+ const paragraph = normalizeRagParagraphText(currentParagraph.join(" "));
3402
+ currentParagraph.length = 0;
3403
+ if (paragraph) paragraphs.push(paragraph);
3404
+ };
3405
+ content.replace(/\\n/g, "\n").split(/\r?\n/).forEach((line) => {
3406
+ const trimmedLine = line.trim();
3407
+ if (!trimmedLine) {
3408
+ flushParagraph();
3409
+ return;
3410
+ }
3411
+ if (HR_PATTERN.test(trimmedLine)) {
3412
+ flushParagraph();
3413
+ return;
3414
+ }
3415
+ currentParagraph.push(trimmedLine);
3416
+ });
3417
+ flushParagraph();
3418
+ return paragraphs;
3419
+ }
3420
+ function getRagAnswerParagraphRoles(paragraphs) {
3421
+ const normalized = paragraphs.map(normalizeRagParagraphText);
3422
+ const roles = normalized.map(() => null);
3423
+ const sourceIndexes = normalized.map((paragraph, index) => isRagSourceParagraph(paragraph) ? index : -1).filter((index) => index >= 0);
3424
+ for (const sourceIndex of sourceIndexes) {
3425
+ roles[sourceIndex] = "source";
3426
+ for (const candidateIndex of [sourceIndex - 1, sourceIndex + 1]) {
3427
+ if (candidateIndex >= 0 && candidateIndex < normalized.length && roles[candidateIndex] == null && isRagFollowUpCandidate(normalized[candidateIndex] ?? "")) {
3428
+ roles[candidateIndex] = "follow-up";
3429
+ }
3430
+ }
3431
+ }
3432
+ return roles;
3433
+ }
3434
+ function getRagAnswerParagraphRole(paragraph, paragraphs) {
3435
+ const normalizedParagraph = normalizeRagParagraphText(paragraph);
3436
+ if (isRagSourceParagraph(normalizedParagraph)) return "source";
3437
+ const roles = getRagAnswerParagraphRoles(paragraphs);
3438
+ return paragraphs.map(normalizeRagParagraphText).some((candidate, index) => {
3439
+ return candidate === normalizedParagraph && roles[index] === "follow-up";
3440
+ }) ? "follow-up" : null;
3441
+ }
3442
+ function ragAnswerRoleClassName(role) {
3443
+ if (role === "source") return RAG_SOURCE_CLASS;
3444
+ if (role === "follow-up") return RAG_FOLLOW_UP_CLASS;
3445
+ return void 0;
3446
+ }
3447
+ function reactNodeText(node) {
3448
+ if (typeof node === "string" || typeof node === "number") return String(node);
3449
+ if (Array.isArray(node)) return node.map(reactNodeText).join("");
3450
+ if (React.isValidElement(node)) {
3451
+ return reactNodeText(node.props.children);
3452
+ }
3453
+ return "";
3454
+ }
3455
+ function buildComponents(onImageClick, isResolvingRef, ragParagraphsRef) {
3505
3456
  return {
3506
- p: ({ children }) => /* @__PURE__ */ jsx("p", { children }),
3457
+ p: ({ children }) => {
3458
+ const role = getRagAnswerParagraphRole(
3459
+ reactNodeText(children),
3460
+ ragParagraphsRef?.current ?? []
3461
+ );
3462
+ return /* @__PURE__ */ jsx("p", { className: ragAnswerRoleClassName(role), children });
3463
+ },
3507
3464
  code: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
3508
3465
  pre: ({ children }) => /* @__PURE__ */ jsx("div", { className: "payman-v2-markdown-pre", children: /* @__PURE__ */ jsx("pre", { children }) }),
3509
3466
  ul: ({ children }) => /* @__PURE__ */ jsx("ul", { children }),
@@ -3541,8 +3498,13 @@ function MarkdownRendererV2({
3541
3498
  }) {
3542
3499
  const isResolvingRef = useRef(isResolvingImages);
3543
3500
  isResolvingRef.current = isResolvingImages;
3501
+ const ragParagraphsRef = useRef([]);
3502
+ ragParagraphsRef.current = useMemo(
3503
+ () => extractRagParagraphTexts(content),
3504
+ [content]
3505
+ );
3544
3506
  const components = useMemo(
3545
- () => buildComponents(onImageClick, isResolvingRef),
3507
+ () => buildComponents(onImageClick, isResolvingRef, ragParagraphsRef),
3546
3508
  [onImageClick]
3547
3509
  );
3548
3510
  return /* @__PURE__ */ jsx(
@@ -4668,18 +4630,28 @@ function useExpiryCountdown(prompt) {
4668
4630
  setSecondsLeft(void 0);
4669
4631
  return;
4670
4632
  }
4671
- setSecondsLeft(initial);
4672
- const id = window.setInterval(() => {
4673
- setSecondsLeft((s) => {
4674
- if (s === void 0) return s;
4675
- if (s <= 1) {
4676
- window.clearInterval(id);
4677
- return 0;
4678
- }
4679
- return s - 1;
4680
- });
4681
- }, 1e3);
4682
- return () => window.clearInterval(id);
4633
+ const deadlineMs = Date.now() + initial * 1e3;
4634
+ let intervalId;
4635
+ const remaining = () => Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3));
4636
+ const sync = () => {
4637
+ const next = remaining();
4638
+ setSecondsLeft(next);
4639
+ if (next === 0 && intervalId !== void 0) {
4640
+ window.clearInterval(intervalId);
4641
+ intervalId = void 0;
4642
+ }
4643
+ };
4644
+ sync();
4645
+ intervalId = window.setInterval(sync, 1e3);
4646
+ document.addEventListener("visibilitychange", sync);
4647
+ window.addEventListener("focus", sync);
4648
+ window.addEventListener("pageshow", sync);
4649
+ return () => {
4650
+ if (intervalId !== void 0) window.clearInterval(intervalId);
4651
+ document.removeEventListener("visibilitychange", sync);
4652
+ window.removeEventListener("focus", sync);
4653
+ window.removeEventListener("pageshow", sync);
4654
+ };
4683
4655
  }, [prompt.userActionId, prompt.subAction, initial]);
4684
4656
  const expired = initial !== void 0 && secondsLeft === 0;
4685
4657
  return [secondsLeft, expired];
@@ -6236,7 +6208,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6236
6208
  const submitUserAction2 = chat.submitUserAction ?? NOOP_ASYNC;
6237
6209
  const cancelUserAction2 = chat.cancelUserAction ?? NOOP_ASYNC;
6238
6210
  const resendUserAction2 = chat.resendUserAction ?? NOOP_ASYNC;
6239
- const expireUserAction = chat.expireUserAction ?? NOOP_ASYNC;
6211
+ const expireUserAction2 = chat.expireUserAction ?? NOOP_ASYNC;
6240
6212
  const dismissNotification = chat.dismissNotification ?? NOOP;
6241
6213
  const isUserActionSupported = typeof chat.submitUserAction === "function" && typeof chat.cancelUserAction === "function" && typeof chat.resendUserAction === "function";
6242
6214
  const {
@@ -6637,7 +6609,7 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6637
6609
  onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
6638
6610
  onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
6639
6611
  onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
6640
- onExpireUserAction: isUserActionSupported ? expireUserAction : void 0,
6612
+ onExpireUserAction: isUserActionSupported ? expireUserAction2 : void 0,
6641
6613
  onDismissNotification: dismissNotification,
6642
6614
  onSubmitFeedback: handleSubmitFeedback
6643
6615
  }