@paymanai/payman-ask-sdk 4.0.19 → 4.0.20

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.js CHANGED
@@ -40,7 +40,10 @@ var remarkBreaks__default = /*#__PURE__*/_interopDefault(remarkBreaks);
40
40
 
41
41
  var __defProp = Object.defineProperty;
42
42
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
43
- var __publicField = (obj, key, value) => __defNormalProp(obj, key + "", value);
43
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
44
+ var __defProp2 = Object.defineProperty;
45
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
46
+ var __publicField2 = (obj, key, value) => __defNormalProp2(obj, key + "", value);
44
47
  function generateId() {
45
48
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
46
49
  const r = Math.random() * 16 | 0;
@@ -736,7 +739,8 @@ function buildRequestBody(config, userMessage, sessionId, options) {
736
739
  sessionAttributes,
737
740
  analysisMode: options?.analysisMode,
738
741
  locale: resolveLocale(config.locale),
739
- timezone: resolveTimezone(config.timezone)
742
+ timezone: resolveTimezone(config.timezone),
743
+ ...options?.attachments?.length ? { attachments: options.attachments } : {}
740
744
  };
741
745
  }
742
746
  function resolveLocale(configured) {
@@ -781,6 +785,7 @@ function buildResolveImagesUrl(config) {
781
785
  const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
782
786
  return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
783
787
  }
788
+ var NGROK_SKIP_BROWSER_WARNING = "ngrok-skip-browser-warning";
784
789
  function buildRequestHeaders(config) {
785
790
  const headers = {
786
791
  ...config.api.headers
@@ -788,6 +793,9 @@ function buildRequestHeaders(config) {
788
793
  if (config.api.authToken) {
789
794
  headers.Authorization = `Bearer ${config.api.authToken}`;
790
795
  }
796
+ if (!headers[NGROK_SKIP_BROWSER_WARNING]) {
797
+ headers[NGROK_SKIP_BROWSER_WARNING] = "true";
798
+ }
791
799
  return headers;
792
800
  }
793
801
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
@@ -1068,10 +1076,85 @@ function createCancelledMessageUpdate(steps, currentMessage) {
1068
1076
  currentMessage: currentMessage || "Thinking..."
1069
1077
  };
1070
1078
  }
1079
+ var DEFAULT_SIGNED_URL_ENDPOINT = "/api/files/signed-url";
1080
+ function fileExtension(filename) {
1081
+ const ext = filename.split(".").pop()?.toLowerCase().replace(/^\./, "");
1082
+ return ext && ext.length > 0 ? ext : "bin";
1083
+ }
1084
+ function resolveMimeType(file) {
1085
+ if (file.type && file.type.trim().length > 0) return file.type;
1086
+ const ext = fileExtension(file.name);
1087
+ const byExt = {
1088
+ pdf: "application/pdf",
1089
+ png: "image/png",
1090
+ jpg: "image/jpeg",
1091
+ jpeg: "image/jpeg",
1092
+ gif: "image/gif",
1093
+ webp: "image/webp"
1094
+ };
1095
+ return byExt[ext] ?? "application/octet-stream";
1096
+ }
1097
+ function buildSignedUrlEndpoint(config, ext) {
1098
+ const endpoint = config.api.signedUrlEndpoint || DEFAULT_SIGNED_URL_ENDPOINT;
1099
+ const queryParams = new URLSearchParams({ extn: ext.replace(/^\./, "") });
1100
+ return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
1101
+ }
1102
+ async function requestSignedUrl(config, ext, signal) {
1103
+ const url = buildSignedUrlEndpoint(config, ext);
1104
+ const headers = buildRequestHeaders(config);
1105
+ const response = await fetch(url, {
1106
+ method: "GET",
1107
+ headers,
1108
+ signal
1109
+ });
1110
+ if (!response.ok) {
1111
+ const errorText = await response.text().catch(() => "");
1112
+ throw new Error(
1113
+ errorText ? `Failed to get upload URL (${response.status}): ${errorText}` : `Failed to get upload URL (${response.status})`
1114
+ );
1115
+ }
1116
+ const data = await response.json();
1117
+ if (!data.key || !data.url) {
1118
+ throw new Error("Signed URL response missing key or url");
1119
+ }
1120
+ return { key: data.key, url: data.url };
1121
+ }
1122
+ async function uploadToSignedUrl(signedUrl, file, mimeType, signal) {
1123
+ const response = await fetch(signedUrl, {
1124
+ method: "PUT",
1125
+ headers: {
1126
+ "Content-Type": mimeType,
1127
+ "x-ms-blob-type": "BlockBlob"
1128
+ },
1129
+ body: file,
1130
+ signal
1131
+ });
1132
+ if (!response.ok) {
1133
+ const errorText = await response.text().catch(() => "");
1134
+ throw new Error(
1135
+ errorText ? `Failed to upload file (${response.status}): ${errorText}` : `Failed to upload file (${response.status})`
1136
+ );
1137
+ }
1138
+ }
1139
+ async function uploadAttachment(config, file, signal) {
1140
+ const ext = fileExtension(file.name);
1141
+ const mimeType = resolveMimeType(file);
1142
+ const { key, url } = await requestSignedUrl(config, ext, signal);
1143
+ await uploadToSignedUrl(url, file, mimeType, signal);
1144
+ return {
1145
+ tempKey: key,
1146
+ filename: file.name,
1147
+ mimeType
1148
+ };
1149
+ }
1150
+ async function uploadAttachments(config, files, signal) {
1151
+ const uploads = files.map((file) => uploadAttachment(config, file, signal));
1152
+ return Promise.all(uploads);
1153
+ }
1071
1154
  var UserActionStaleError = class extends Error {
1072
1155
  constructor(userActionId, message = "User action is no longer actionable") {
1073
1156
  super(message);
1074
- __publicField(this, "userActionId");
1157
+ __publicField2(this, "userActionId");
1075
1158
  this.name = "UserActionStaleError";
1076
1159
  this.userActionId = userActionId;
1077
1160
  }
@@ -1104,9 +1187,6 @@ async function cancelUserAction(config, userActionId) {
1104
1187
  async function resendUserAction(config, userActionId) {
1105
1188
  return sendUserActionRequest(config, userActionId, "resend");
1106
1189
  }
1107
- async function expireUserAction(config, userActionId) {
1108
- return sendUserActionRequest(config, userActionId, "expired");
1109
- }
1110
1190
  var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1111
1191
  function upsertPrompt(prompts, req) {
1112
1192
  const active = { ...req, status: "pending" };
@@ -1135,12 +1215,32 @@ function getStoredOrInitialMessages(config) {
1135
1215
  function getSessionIdFromMessages(messages) {
1136
1216
  return messages.find((message) => message.sessionId)?.sessionId;
1137
1217
  }
1218
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "gif", "webp", "avif"]);
1219
+ function attachmentKindFromFile(file) {
1220
+ const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
1221
+ if (IMAGE_EXTENSIONS.has(ext) || file.type.startsWith("image/")) return "image";
1222
+ return "file";
1223
+ }
1224
+ function buildMessageAttachments(files) {
1225
+ return files.map((file, index) => {
1226
+ const kind = attachmentKindFromFile(file);
1227
+ return {
1228
+ id: `att-${Date.now()}-${index}`,
1229
+ filename: file.name,
1230
+ mimeType: file.type || "application/octet-stream",
1231
+ // Blob URL for all local files so PDFs/docs stay previewable after send.
1232
+ previewUrl: URL.createObjectURL(file),
1233
+ kind
1234
+ };
1235
+ });
1236
+ }
1138
1237
  function useChatV2(config, callbacks = {}) {
1139
1238
  const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
1140
1239
  const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
1141
1240
  if (!config.userId) return false;
1142
1241
  return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1143
1242
  });
1243
+ const [isUploadingAttachments, setIsUploadingAttachments] = react.useState(false);
1144
1244
  const sessionIdRef = react.useRef(
1145
1245
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1146
1246
  );
@@ -1215,21 +1315,45 @@ function useChatV2(config, callbacks = {}) {
1215
1315
  );
1216
1316
  const sendMessage = react.useCallback(
1217
1317
  async (userMessage, options) => {
1218
- if (!userMessage.trim()) return;
1318
+ const trimmedMessage = userMessage.trim();
1319
+ const files = options?.files ?? [];
1320
+ const hasPreuploadedAttachments = (options?.attachments?.length ?? 0) > 0;
1321
+ if (!trimmedMessage && files.length === 0 && !hasPreuploadedAttachments) return;
1322
+ let streamAttachments = options?.attachments;
1323
+ if (!streamAttachments && files.length > 0) {
1324
+ setIsUploadingAttachments(true);
1325
+ try {
1326
+ streamAttachments = await uploadAttachments(configRef.current, files);
1327
+ } catch (error) {
1328
+ if (error.name !== "AbortError") {
1329
+ callbacksRef.current.onError?.(error);
1330
+ }
1331
+ throw error;
1332
+ } finally {
1333
+ setIsUploadingAttachments(false);
1334
+ }
1335
+ }
1219
1336
  if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1220
1337
  sessionIdRef.current = generateId();
1221
1338
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1222
1339
  }
1340
+ const messageAttachments = files.length > 0 ? buildMessageAttachments(files) : streamAttachments?.length ? streamAttachments.map((attachment, index) => ({
1341
+ id: `att-${Date.now()}-${index}`,
1342
+ filename: attachment.filename,
1343
+ mimeType: attachment.mimeType,
1344
+ kind: attachment.mimeType.startsWith("image/") ? "image" : "file"
1345
+ })) : void 0;
1223
1346
  const userMessageId = `user-${Date.now()}`;
1224
1347
  const userMsg = {
1225
1348
  id: userMessageId,
1226
1349
  sessionId: sessionIdRef.current,
1227
1350
  role: "user",
1228
- content: userMessage,
1229
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1351
+ content: trimmedMessage,
1352
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1353
+ attachments: messageAttachments
1230
1354
  };
1231
1355
  setMessages((prev) => [...prev, userMsg]);
1232
- callbacksRef.current.onMessageSent?.(userMessage);
1356
+ callbacksRef.current.onMessageSent?.(trimmedMessage);
1233
1357
  setIsWaitingForResponse(true);
1234
1358
  callbacksRef.current.onStreamStart?.();
1235
1359
  const streamingId = `assistant-${Date.now()}`;
@@ -1256,11 +1380,14 @@ function useChatV2(config, callbacks = {}) {
1256
1380
  activeStreamStore.start(userId, abortController, initialMessages);
1257
1381
  }
1258
1382
  const newSessionId = await startStream(
1259
- userMessage,
1383
+ trimmedMessage,
1260
1384
  streamingId,
1261
1385
  sessionIdRef.current,
1262
1386
  abortController,
1263
- options
1387
+ {
1388
+ analysisMode: options?.analysisMode,
1389
+ attachments: streamAttachments
1390
+ }
1264
1391
  );
1265
1392
  const finalStreamUserId = streamUserIdRef.current ?? userId;
1266
1393
  if (finalStreamUserId) {
@@ -1398,19 +1525,6 @@ function useChatV2(config, callbacks = {}) {
1398
1525
  },
1399
1526
  [setPromptStatus]
1400
1527
  );
1401
- const expireUserAction2 = react.useCallback(
1402
- async (userActionId) => {
1403
- setPromptStatus(userActionId, "expired");
1404
- try {
1405
- await expireUserAction(configRef.current, userActionId);
1406
- } catch (error) {
1407
- if (error instanceof UserActionStaleError) return;
1408
- callbacksRef.current.onError?.(error);
1409
- throw error;
1410
- }
1411
- },
1412
- [setPromptStatus]
1413
- );
1414
1528
  const dismissNotification = react.useCallback((id) => {
1415
1529
  setUserActionState((prev) => ({
1416
1530
  ...prev,
@@ -1451,6 +1565,18 @@ function useChatV2(config, callbacks = {}) {
1451
1565
  setMessages(config.initialMessages);
1452
1566
  sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
1453
1567
  }, [config.initialMessages, config.initialSessionId, config.userId]);
1568
+ const hydratedSessionIdRef = react.useRef(void 0);
1569
+ react.useEffect(() => {
1570
+ if (config.userId) return;
1571
+ if (!config.initialMessages?.length) return;
1572
+ const sessionId = config.initialSessionId;
1573
+ if (hydratedSessionIdRef.current === sessionId && messagesRef.current.length > 0) {
1574
+ return;
1575
+ }
1576
+ hydratedSessionIdRef.current = sessionId;
1577
+ setMessages(config.initialMessages);
1578
+ sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? sessionId;
1579
+ }, [config.initialMessages, config.initialSessionId, config.userId]);
1454
1580
  react.useEffect(() => {
1455
1581
  const prevUserId = prevUserIdRef.current;
1456
1582
  prevUserIdRef.current = config.userId;
@@ -1485,12 +1611,12 @@ function useChatV2(config, callbacks = {}) {
1485
1611
  getSessionId,
1486
1612
  getMessages,
1487
1613
  isWaitingForResponse,
1614
+ isUploadingAttachments,
1488
1615
  sessionId: sessionIdRef.current,
1489
1616
  userActionState,
1490
1617
  submitUserAction: submitUserAction2,
1491
1618
  cancelUserAction: cancelUserAction2,
1492
1619
  resendUserAction: resendUserAction2,
1493
- expireUserAction: expireUserAction2,
1494
1620
  dismissNotification
1495
1621
  };
1496
1622
  }
@@ -1709,6 +1835,102 @@ function useVoice(config = {}, callbacks = {}) {
1709
1835
  reset
1710
1836
  };
1711
1837
  }
1838
+ function useAttachmentUpload(config) {
1839
+ const configRef = react.useRef(config);
1840
+ configRef.current = config;
1841
+ const [entries, setEntries] = react.useState(/* @__PURE__ */ new Map());
1842
+ const [orderedIds, setOrderedIds] = react.useState([]);
1843
+ const abortControllersRef = react.useRef(/* @__PURE__ */ new Map());
1844
+ const activeIdsRef = react.useRef(/* @__PURE__ */ new Set());
1845
+ const startUpload = react.useCallback((id, file) => {
1846
+ if (activeIdsRef.current.has(id)) return;
1847
+ activeIdsRef.current.add(id);
1848
+ const controller = new AbortController();
1849
+ abortControllersRef.current.set(id, controller);
1850
+ setEntries((prev) => {
1851
+ const next = new Map(prev);
1852
+ next.set(id, { id, status: "uploading" });
1853
+ return next;
1854
+ });
1855
+ void uploadAttachment(configRef.current, file, controller.signal).then((payload) => {
1856
+ if (controller.signal.aborted) return;
1857
+ setEntries((prev) => {
1858
+ const next = new Map(prev);
1859
+ next.set(id, { id, status: "done", payload });
1860
+ return next;
1861
+ });
1862
+ }).catch((error) => {
1863
+ if (error.name === "AbortError") return;
1864
+ setEntries((prev) => {
1865
+ const next = new Map(prev);
1866
+ next.set(id, {
1867
+ id,
1868
+ status: "error",
1869
+ error: error.message || "Upload failed"
1870
+ });
1871
+ return next;
1872
+ });
1873
+ }).finally(() => {
1874
+ abortControllersRef.current.delete(id);
1875
+ });
1876
+ }, []);
1877
+ const syncAttachments = react.useCallback(
1878
+ (attachments) => {
1879
+ const nextIds = attachments.map((attachment) => attachment.id);
1880
+ setOrderedIds(nextIds);
1881
+ const nextIdSet = new Set(nextIds);
1882
+ for (const id of activeIdsRef.current) {
1883
+ if (nextIdSet.has(id)) continue;
1884
+ abortControllersRef.current.get(id)?.abort();
1885
+ abortControllersRef.current.delete(id);
1886
+ activeIdsRef.current.delete(id);
1887
+ }
1888
+ setEntries((prev) => {
1889
+ const next = new Map(prev);
1890
+ for (const id of next.keys()) {
1891
+ if (!nextIdSet.has(id)) next.delete(id);
1892
+ }
1893
+ return next;
1894
+ });
1895
+ for (const attachment of attachments) {
1896
+ startUpload(attachment.id, attachment.file);
1897
+ }
1898
+ },
1899
+ [startUpload]
1900
+ );
1901
+ const clearAll = react.useCallback(() => {
1902
+ for (const controller of abortControllersRef.current.values()) {
1903
+ controller.abort();
1904
+ }
1905
+ abortControllersRef.current.clear();
1906
+ activeIdsRef.current.clear();
1907
+ setOrderedIds([]);
1908
+ setEntries(/* @__PURE__ */ new Map());
1909
+ }, []);
1910
+ const entryList = react.useMemo(
1911
+ () => orderedIds.map((id) => entries.get(id)).filter((entry) => entry != null),
1912
+ [entries, orderedIds]
1913
+ );
1914
+ const isUploading = entryList.some((entry) => entry.status === "uploading");
1915
+ const hasErrors = entryList.some((entry) => entry.status === "error");
1916
+ const allReady = orderedIds.length === 0 || orderedIds.every((id) => entries.get(id)?.status === "done");
1917
+ const payloads = entryList.filter((entry) => entry.status === "done" && entry.payload).map((entry) => entry.payload);
1918
+ const statusById = react.useMemo(
1919
+ () => Object.fromEntries(
1920
+ entryList.map((entry) => [entry.id, entry.status])
1921
+ ),
1922
+ [entryList]
1923
+ );
1924
+ return {
1925
+ syncAttachments,
1926
+ clearAll,
1927
+ isUploading,
1928
+ hasErrors,
1929
+ allReady,
1930
+ payloads,
1931
+ statusById
1932
+ };
1933
+ }
1712
1934
  function classifyField(field) {
1713
1935
  if (!field) return "text";
1714
1936
  if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
@@ -1825,6 +2047,66 @@ function buildContent(schema, values) {
1825
2047
  }
1826
2048
  return content;
1827
2049
  }
2050
+ var ATTACHMENTS_SUFFIX_RE = /\n\n\[Attachments:[^\]]*\]\s*$/;
2051
+ function stripAttachmentsSuffixFromIntent(intent) {
2052
+ return intent.replace(ATTACHMENTS_SUFFIX_RE, "").trimEnd();
2053
+ }
2054
+ function mapFeedback(feedback) {
2055
+ if (!feedback?.feedback) return null;
2056
+ return feedback.feedback === "POSITIVE" ? "up" : "down";
2057
+ }
2058
+ function mapHistoryAttachments(executionId, attachments) {
2059
+ const mapped = (attachments ?? []).map((attachment, index) => ({
2060
+ id: `${executionId}:att:${index}`,
2061
+ filename: attachment.filename,
2062
+ mimeType: attachment.mimeType,
2063
+ url: attachment.url ?? void 0,
2064
+ kind: attachment.mimeType.startsWith("image/") ? "image" : "file"
2065
+ }));
2066
+ return mapped.length > 0 ? mapped : void 0;
2067
+ }
2068
+ function mapExecutionHistoryToChatMessages(message) {
2069
+ const timestamp = message.startTime || message.endTime || (/* @__PURE__ */ new Date()).toISOString();
2070
+ const executionId = message.executionId ?? message.traceId ?? message.id;
2071
+ const attachments = mapHistoryAttachments(message.id, message.attachments);
2072
+ const rows = [
2073
+ {
2074
+ id: `${message.id}:user`,
2075
+ role: "user",
2076
+ content: stripAttachmentsSuffixFromIntent(message.sessionUserIntent),
2077
+ timestamp,
2078
+ attachments
2079
+ }
2080
+ ];
2081
+ if (message.agentResponse) {
2082
+ rows.push({
2083
+ id: `${message.id}:assistant`,
2084
+ role: "assistant",
2085
+ content: message.agentResponse,
2086
+ timestamp: message.endTime || timestamp,
2087
+ isError: message.status === "FAILED",
2088
+ executionId,
2089
+ feedback: mapFeedback(message.feedback)
2090
+ });
2091
+ }
2092
+ return rows;
2093
+ }
2094
+ function mapExecutionHistoryPageToChatMessages(messages) {
2095
+ return messages.flatMap(mapExecutionHistoryToChatMessages);
2096
+ }
2097
+ function attachmentDisplayUrl(attachment) {
2098
+ return attachment.previewUrl ?? attachment.url;
2099
+ }
2100
+ function isImageAttachment(attachment) {
2101
+ const displayUrl = attachmentDisplayUrl(attachment);
2102
+ return attachment.kind === "image" || Boolean(displayUrl) && attachment.mimeType.startsWith("image/");
2103
+ }
2104
+ function isPdfAttachmentMeta(attachment) {
2105
+ return attachment.mimeType === "application/pdf" || /\.pdf$/i.test(attachment.filename);
2106
+ }
2107
+ function isPdfFile(file) {
2108
+ return file.type === "application/pdf" || /\.pdf$/i.test(file.name);
2109
+ }
1828
2110
  var PaymanChatContext = react.createContext(void 0);
1829
2111
  function usePaymanChat() {
1830
2112
  const context = react.useContext(PaymanChatContext);
@@ -1935,6 +2217,111 @@ function subscribeToCfRay(urlPattern, listener) {
1935
2217
  };
1936
2218
  }
1937
2219
 
2220
+ // src/utils/attachmentConfig.ts
2221
+ var DEFAULT_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp"];
2222
+ var DEFAULT_DOCUMENT_EXTENSIONS = ["pdf", "docx", "xlsx", "xls"];
2223
+ function resolveChatAttachmentConfig(config) {
2224
+ const nested = config.attachments;
2225
+ const enabled = nested?.enabled ?? config.showAttachmentButton ?? true;
2226
+ const uploadImage = nested?.uploadImage ?? config.showUploadImageButton ?? true;
2227
+ const attachFile = nested?.attachFile ?? config.showAttachFileButton ?? true;
2228
+ return {
2229
+ showAttachmentButton: enabled && (uploadImage || attachFile),
2230
+ showUploadImageButton: enabled && uploadImage,
2231
+ showAttachFileButton: enabled && attachFile,
2232
+ maxCount: nested?.maxCount ?? nested?.maxImages ?? nested?.maxDocuments,
2233
+ maxFileBytes: nested?.maxFileBytes,
2234
+ maxTotalBytes: nested?.maxTotalBytes,
2235
+ allowedImageExtensions: nested?.imageExtensions ?? DEFAULT_IMAGE_EXTENSIONS,
2236
+ allowedFileExtensions: nested?.documentExtensions ?? DEFAULT_DOCUMENT_EXTENSIONS
2237
+ };
2238
+ }
2239
+
2240
+ // src/utils/formatAttachmentBytes.ts
2241
+ function formatAttachmentBytes(bytes) {
2242
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
2243
+ const units = ["B", "KB", "MB", "GB"];
2244
+ let value = bytes;
2245
+ let unitIndex = 0;
2246
+ while (value >= 1024 && unitIndex < units.length - 1) {
2247
+ value /= 1024;
2248
+ unitIndex += 1;
2249
+ }
2250
+ const rounded = unitIndex === 0 ? String(Math.round(value)) : value >= 10 ? value.toFixed(0) : value.toFixed(1).replace(/\.0$/, "");
2251
+ return `${rounded} ${units[unitIndex]}`;
2252
+ }
2253
+
2254
+ // src/utils/pdfLink.ts
2255
+ function filenameFromContentDisposition(value) {
2256
+ if (!value) return void 0;
2257
+ let decoded = value;
2258
+ try {
2259
+ decoded = decodeURIComponent(value);
2260
+ } catch {
2261
+ decoded = value;
2262
+ }
2263
+ const quoted = decoded.match(/filename\*=(?:UTF-8''([^;]+)|"([^"]+)")/i);
2264
+ if (quoted?.[1] || quoted?.[2]) {
2265
+ return (quoted[1] ?? quoted[2])?.trim();
2266
+ }
2267
+ const unquoted = decoded.match(/filename=([^;]+)/i);
2268
+ return unquoted?.[1]?.replace(/"/g, "").trim();
2269
+ }
2270
+ function filenameFromUrlPath(href) {
2271
+ try {
2272
+ const parts = new URL(href).pathname.split("/").filter(Boolean);
2273
+ const last = parts[parts.length - 1];
2274
+ return last ? decodeURIComponent(last) : void 0;
2275
+ } catch {
2276
+ return void 0;
2277
+ }
2278
+ }
2279
+ function isPdfUrl(href) {
2280
+ if (!href) return false;
2281
+ const lower = href.toLowerCase();
2282
+ if (lower.includes(".pdf")) return true;
2283
+ try {
2284
+ const url = new URL(href);
2285
+ if (url.pathname.toLowerCase().endsWith(".pdf")) return true;
2286
+ const filename = url.searchParams.get("filename");
2287
+ if (filename?.toLowerCase().includes(".pdf")) return true;
2288
+ for (const key of ["rscd", "response-content-disposition"]) {
2289
+ const fromDisposition = filenameFromContentDisposition(
2290
+ url.searchParams.get(key)
2291
+ );
2292
+ if (fromDisposition?.toLowerCase().includes(".pdf")) return true;
2293
+ }
2294
+ } catch {
2295
+ return lower.endsWith(".pdf");
2296
+ }
2297
+ return false;
2298
+ }
2299
+ function getPdfTitleFromUrl(href, linkText) {
2300
+ const text = linkText?.trim();
2301
+ if (text) return text;
2302
+ try {
2303
+ const url = new URL(href);
2304
+ const filename = url.searchParams.get("filename");
2305
+ if (filename) {
2306
+ return filename.replace(/\.pdf$/i, "").replace(/[-_]+/g, " ").trim();
2307
+ }
2308
+ for (const key of ["rscd", "response-content-disposition"]) {
2309
+ const fromDisposition = filenameFromContentDisposition(
2310
+ url.searchParams.get(key)
2311
+ );
2312
+ if (fromDisposition) {
2313
+ return fromDisposition.replace(/\.pdf$/i, "").replace(/[-_]+/g, " ").trim();
2314
+ }
2315
+ }
2316
+ } catch {
2317
+ }
2318
+ const fromPath = filenameFromUrlPath(href);
2319
+ if (fromPath) {
2320
+ return fromPath.replace(/\.pdf$/i, "").replace(/[-_]+/g, " ").trim();
2321
+ }
2322
+ return "Document";
2323
+ }
2324
+
1938
2325
  // src/utils/slashCommands.ts
1939
2326
  var DEFAULT_SLASH_COMMANDS = [
1940
2327
  {
@@ -3086,6 +3473,108 @@ function ActionTooltipV2({ label, children }) {
3086
3473
  ] }) })
3087
3474
  ] });
3088
3475
  }
3476
+ function FilePreviewShell({
3477
+ filename,
3478
+ typeLabel,
3479
+ onClick,
3480
+ thumbnail,
3481
+ className,
3482
+ "aria-label": ariaLabel
3483
+ }) {
3484
+ const shellClass = cn(
3485
+ "payman-v2-file-preview-shell",
3486
+ "payman-v2-file-preview-shell-sent",
3487
+ onClick && "payman-v2-file-preview-shell-clickable",
3488
+ className
3489
+ );
3490
+ const body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3491
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-file-preview-thumb", children: thumbnail ?? /* @__PURE__ */ jsxRuntime.jsx(
3492
+ lucideReact.FileText,
3493
+ {
3494
+ size: 16,
3495
+ strokeWidth: 1.75,
3496
+ className: "payman-v2-file-preview-doc-icon"
3497
+ }
3498
+ ) }),
3499
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-file-preview-info", children: [
3500
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-file-preview-name", children: filename }),
3501
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-file-preview-type", children: typeLabel })
3502
+ ] })
3503
+ ] });
3504
+ if (onClick) {
3505
+ return /* @__PURE__ */ jsxRuntime.jsx(
3506
+ "button",
3507
+ {
3508
+ type: "button",
3509
+ className: shellClass,
3510
+ onClick,
3511
+ "aria-label": ariaLabel ?? `Open ${filename}`,
3512
+ children: body
3513
+ }
3514
+ );
3515
+ }
3516
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: shellClass, children: body });
3517
+ }
3518
+ function FilePreviewBlockLayout({
3519
+ children,
3520
+ className,
3521
+ ...rest
3522
+ }) {
3523
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("payman-v2-file-preview-item", className), ...rest, children });
3524
+ }
3525
+ function attachmentTypeLabel(attachment) {
3526
+ if (isImageAttachment(attachment)) {
3527
+ return "Image";
3528
+ }
3529
+ const ext = attachment.filename.split(".").pop()?.toUpperCase();
3530
+ return ext || "Document";
3531
+ }
3532
+ function isPdfAttachment(attachment) {
3533
+ const displayUrl = attachmentDisplayUrl(attachment);
3534
+ return isPdfAttachmentMeta(attachment) || Boolean(displayUrl) && isPdfUrl(displayUrl);
3535
+ }
3536
+ function AttachmentPreviewBlock({
3537
+ attachment,
3538
+ onImageClick
3539
+ }) {
3540
+ const chatContext = react.useContext(PaymanChatContext);
3541
+ const displayUrl = attachmentDisplayUrl(attachment);
3542
+ const typeLabel = attachmentTypeLabel(attachment);
3543
+ const isImage = isImageAttachment(attachment) && displayUrl;
3544
+ const openAttachment = () => {
3545
+ if (!displayUrl) return;
3546
+ if (isImageAttachment(attachment)) {
3547
+ onImageClick?.(displayUrl, attachment.filename);
3548
+ return;
3549
+ }
3550
+ if (isPdfAttachment(attachment)) {
3551
+ chatContext?.openPdfSheet(displayUrl, attachment.filename);
3552
+ return;
3553
+ }
3554
+ window.open(displayUrl, "_blank", "noopener,noreferrer");
3555
+ };
3556
+ if (isImage) {
3557
+ return /* @__PURE__ */ jsxRuntime.jsx(FilePreviewBlockLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(
3558
+ FilePreviewShell,
3559
+ {
3560
+ filename: attachment.filename,
3561
+ typeLabel,
3562
+ onClick: openAttachment,
3563
+ "aria-label": `Preview ${attachment.filename}`,
3564
+ thumbnail: /* @__PURE__ */ jsxRuntime.jsx("img", { src: displayUrl, alt: "", draggable: false })
3565
+ }
3566
+ ) });
3567
+ }
3568
+ return /* @__PURE__ */ jsxRuntime.jsx(FilePreviewBlockLayout, { children: /* @__PURE__ */ jsxRuntime.jsx(
3569
+ FilePreviewShell,
3570
+ {
3571
+ filename: attachment.filename,
3572
+ typeLabel,
3573
+ onClick: displayUrl ? openAttachment : void 0,
3574
+ "aria-label": `Open ${attachment.filename}`
3575
+ }
3576
+ ) });
3577
+ }
3089
3578
  function formatMessageTime(timestamp) {
3090
3579
  const value = new Date(timestamp);
3091
3580
  if (Number.isNaN(value.getTime())) return "";
@@ -3098,6 +3587,7 @@ function UserMessageV2({
3098
3587
  message,
3099
3588
  onEdit,
3100
3589
  onRetry,
3590
+ onImageClick,
3101
3591
  retryDisabled = false,
3102
3592
  actions
3103
3593
  }) {
@@ -3180,7 +3670,15 @@ function UserMessageV2({
3180
3670
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3181
3671
  toastPortal,
3182
3672
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-user-msg payman-v2-fade-in", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-user-msg-group", children: [
3183
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-user-msg-bubble", children: parsedCommand ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-user-msg-command", children: [
3673
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-file-preview payman-v2-user-msg-attachments", children: message.attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(
3674
+ AttachmentPreviewBlock,
3675
+ {
3676
+ attachment,
3677
+ onImageClick
3678
+ },
3679
+ attachment.id
3680
+ )) }),
3681
+ (message.content.trim().length > 0 || !message.attachments?.length) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-user-msg-bubble", children: parsedCommand ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-user-msg-command", children: [
3184
3682
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-user-msg-command-chip", children: parsedCommand.command }),
3185
3683
  parsedCommand.body.trim() ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-user-msg-text", children: parsedCommand.body }) : null
3186
3684
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-user-msg-text", children: message.content }) }),
@@ -3405,9 +3903,44 @@ function MarkdownImageV2({
3405
3903
  }
3406
3904
  ) });
3407
3905
  }
3408
- function buildComponents(onImageClick, isResolvingRef) {
3906
+ function PdfBlockV2({ title, href, onOpen, autoOpen = false }) {
3907
+ const autoOpenedHrefRef = react.useRef(null);
3908
+ react.useEffect(() => {
3909
+ if (!autoOpen || autoOpenedHrefRef.current === href) return;
3910
+ autoOpenedHrefRef.current = href;
3911
+ onOpen(href, title, { auto: true });
3912
+ }, [href, autoOpen]);
3913
+ return /* @__PURE__ */ jsxRuntime.jsx(FilePreviewBlockLayout, { "data-payman-file-block": true, children: /* @__PURE__ */ jsxRuntime.jsx(
3914
+ FilePreviewShell,
3915
+ {
3916
+ filename: title,
3917
+ typeLabel: "PDF",
3918
+ onClick: () => onOpen(href, title),
3919
+ "aria-label": `Open PDF: ${title}`
3920
+ }
3921
+ ) });
3922
+ }
3923
+ function childrenToText(children) {
3924
+ if (typeof children === "string") return children;
3925
+ if (typeof children === "number") return String(children);
3926
+ if (Array.isArray(children)) return children.map(childrenToText).join("");
3927
+ if (children && typeof children === "object" && "props" in children) {
3928
+ return childrenToText(children.props.children);
3929
+ }
3930
+ return "";
3931
+ }
3932
+ function isFileBlockChild(child) {
3933
+ return react.isValidElement(child) && typeof child.props === "object" && child.props !== null && "data-payman-file-block" in child.props;
3934
+ }
3935
+ function buildComponents(onImageClick, isResolvingRef, onPdfClick, autoOpenPdf) {
3409
3936
  return {
3410
- p: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("p", { children }),
3937
+ p: ({ children }) => {
3938
+ const childArray = react.Children.toArray(children);
3939
+ if (childArray.length === 1 && isFileBlockChild(childArray[0])) {
3940
+ return childArray[0];
3941
+ }
3942
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { children });
3943
+ },
3411
3944
  code: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("code", { children }),
3412
3945
  pre: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-markdown-pre", children: /* @__PURE__ */ jsxRuntime.jsx("pre", { children }) }),
3413
3946
  ul: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("ul", { children }),
@@ -3420,7 +3953,15 @@ function buildComponents(onImageClick, isResolvingRef) {
3420
3953
  em: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("em", { children }),
3421
3954
  blockquote: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("blockquote", { children }),
3422
3955
  hr: () => /* @__PURE__ */ jsxRuntime.jsx("hr", {}),
3423
- a: ({ href, children }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children }),
3956
+ a: ({ href, children }) => {
3957
+ const url = href ?? "";
3958
+ if (onPdfClick && isPdfUrl(url)) {
3959
+ const linkText = childrenToText(children).trim();
3960
+ const title = getPdfTitleFromUrl(url, linkText);
3961
+ return /* @__PURE__ */ jsxRuntime.jsx(PdfBlockV2, { href: url, title, onOpen: onPdfClick, autoOpen: autoOpenPdf });
3962
+ }
3963
+ return /* @__PURE__ */ jsxRuntime.jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children });
3964
+ },
3424
3965
  img: ({ src, alt }) => /* @__PURE__ */ jsxRuntime.jsx(
3425
3966
  MarkdownImageV2,
3426
3967
  {
@@ -3441,13 +3982,15 @@ function MarkdownRendererV2({
3441
3982
  content,
3442
3983
  isStreaming,
3443
3984
  isResolvingImages,
3444
- onImageClick
3985
+ onImageClick,
3986
+ onPdfClick,
3987
+ autoOpenPdf
3445
3988
  }) {
3446
3989
  const isResolvingRef = react.useRef(isResolvingImages);
3447
3990
  isResolvingRef.current = isResolvingImages;
3448
3991
  const components = react.useMemo(
3449
- () => buildComponents(onImageClick, isResolvingRef),
3450
- [onImageClick]
3992
+ () => buildComponents(onImageClick, isResolvingRef, onPdfClick, autoOpenPdf),
3993
+ [onImageClick, onPdfClick, autoOpenPdf]
3451
3994
  );
3452
3995
  return /* @__PURE__ */ jsxRuntime.jsx(
3453
3996
  "div",
@@ -3816,6 +4359,17 @@ function stripIncompleteImageToken(text) {
3816
4359
  if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
3817
4360
  return text.slice(0, lastBang);
3818
4361
  }
4362
+ function stripIncompleteLinkToken(text) {
4363
+ const lastBracket = text.lastIndexOf("[");
4364
+ if (lastBracket === -1) return text;
4365
+ if (lastBracket > 0 && text[lastBracket - 1] === "!") return text;
4366
+ const after = text.slice(lastBracket);
4367
+ if (/^\[[^\]]*\]\([^)]*\)/.test(after)) return text;
4368
+ return text.slice(0, lastBracket);
4369
+ }
4370
+ function stripIncompleteMarkdownTokens(text) {
4371
+ return stripIncompleteLinkToken(stripIncompleteImageToken(text));
4372
+ }
3819
4373
  function getFeedbackState(message) {
3820
4374
  const feedback = message.feedback;
3821
4375
  if (feedback === "up" || feedback === "down") return feedback;
@@ -3839,6 +4393,12 @@ function AssistantMessageV2({
3839
4393
  () => getFeedbackState(message)
3840
4394
  );
3841
4395
  const [reasonModalOpen, setReasonModalOpen] = react.useState(false);
4396
+ const chatContext = react.useContext(PaymanChatContext);
4397
+ const chatContextRef = react.useRef(chatContext);
4398
+ chatContextRef.current = chatContext;
4399
+ const handlePdfClick = react.useCallback((href, title, options) => {
4400
+ chatContextRef.current?.openPdfSheet(href, title, options);
4401
+ }, []);
3842
4402
  const canSubmitFeedback = !!onSubmitFeedback && !!message.executionId;
3843
4403
  const [toast, setToast] = react.useState(null);
3844
4404
  const copyResetTimerRef = react.useRef(null);
@@ -3863,7 +4423,7 @@ function AssistantMessageV2({
3863
4423
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
3864
4424
  if (!raw) return "";
3865
4425
  const normalized = raw.replace(/\\n/g, "\n");
3866
- return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
4426
+ return message.isStreaming ? stripIncompleteMarkdownTokens(normalized) : normalized;
3867
4427
  })();
3868
4428
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
3869
4429
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
@@ -4033,10 +4593,12 @@ function AssistantMessageV2({
4033
4593
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-assistant-msg-content-area", children: displayContent ? /* @__PURE__ */ jsxRuntime.jsx(
4034
4594
  MarkdownRendererV2,
4035
4595
  {
4036
- content: displayContent,
4596
+ content: message.isStreaming && !isCancelled || isResponseTyping ? stripIncompleteMarkdownTokens(displayContent) : displayContent,
4037
4597
  isStreaming: message.isStreaming && !isCancelled || isResponseTyping,
4038
4598
  isResolvingImages: message.isResolvingImages,
4039
- onImageClick
4599
+ onImageClick,
4600
+ onPdfClick: handlePdfClick,
4601
+ autoOpenPdf: hasEverStreamed.current
4040
4602
  }
4041
4603
  ) : !isThinkingStreaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-assistant-msg-placeholder", children: "..." }) : null }),
4042
4604
  isCancelled && message.isStreaming && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-assistant-msg-paused", children: [
@@ -4253,7 +4815,6 @@ function VerificationInline({
4253
4815
  const [code, setCode] = react.useState("");
4254
4816
  const [errored, setErrored] = react.useState(false);
4255
4817
  const [resendSec, setResendSec] = react.useState(0);
4256
- const [hiddenAfterResend, setHiddenAfterResend] = react.useState(false);
4257
4818
  const lastSubmittedRef = react.useRef(null);
4258
4819
  const resendTimerRef = react.useRef(void 0);
4259
4820
  const status = prompt.status;
@@ -4267,9 +4828,6 @@ function VerificationInline({
4267
4828
  lastSubmittedRef.current = null;
4268
4829
  }
4269
4830
  }, [prompt.subAction, prompt.userActionId]);
4270
- react.useEffect(() => {
4271
- setHiddenAfterResend(false);
4272
- }, [prompt.expirySeconds, prompt.message, prompt.subAction, prompt.userActionId]);
4273
4831
  react.useEffect(() => {
4274
4832
  if (code.length < codeLen) lastSubmittedRef.current = null;
4275
4833
  }, [code, codeLen]);
@@ -4320,13 +4878,11 @@ function VerificationInline({
4320
4878
  if (locked || resendSec > 0) return;
4321
4879
  setErrored(false);
4322
4880
  setCode("");
4323
- setHiddenAfterResend(true);
4324
4881
  lastSubmittedRef.current = null;
4325
4882
  try {
4326
4883
  await onResend(prompt.userActionId);
4327
4884
  startResendCooldown();
4328
4885
  } catch {
4329
- setHiddenAfterResend(false);
4330
4886
  }
4331
4887
  }, [locked, onResend, prompt.userActionId, resendSec, startResendCooldown]);
4332
4888
  const handleCancel = react.useCallback(() => {
@@ -4334,7 +4890,6 @@ function VerificationInline({
4334
4890
  void onCancel(prompt.userActionId);
4335
4891
  }, [busy, onCancel, prompt.userActionId]);
4336
4892
  const description = prompt.message?.trim() || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
4337
- if (hiddenAfterResend) return null;
4338
4893
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-ua", role: "group", "aria-label": "Verification required", children: [
4339
4894
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-ua-head", children: [
4340
4895
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ShieldCheck, { className: "payman-v2-ua-icon", size: 15, strokeWidth: 1.75, "aria-hidden": true }),
@@ -4430,9 +4985,6 @@ function SchemaFormInline({
4430
4985
  const busy = status === "submitting";
4431
4986
  const stale = status === "stale";
4432
4987
  const locked = busy || stale || expired;
4433
- const isUserConfirmation = prompt.subAction === "UserConfirmation";
4434
- const messageFormat = prompt.metadata?.["payman/messageFormat"];
4435
- const renderMarkdown = messageFormat === "markdown";
4436
4988
  const setValue = (key, value) => {
4437
4989
  setValues((prev) => ({ ...prev, [key]: value }));
4438
4990
  setErrors((prev) => {
@@ -4458,8 +5010,8 @@ function SchemaFormInline({
4458
5010
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-title", children: "Action required" }),
4459
5011
  typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
4460
5012
  ] }),
4461
- prompt.message?.trim() && (renderMarkdown ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-ua-markdown", children: /* @__PURE__ */ jsxRuntime.jsx(MarkdownRendererV2, { content: prompt.message }) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-desc", children: prompt.message })),
4462
- stale ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : fields.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-ua-form", children: fields.map(([key, field]) => {
5013
+ prompt.message?.trim() && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-desc", children: prompt.message }),
5014
+ stale ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : fields.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-ua-desc", children: "This action has no inputs to fill." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-ua-form", children: fields.map(([key, field]) => {
4463
5015
  const widget = classifyField(field);
4464
5016
  const label = field.title || key;
4465
5017
  const required = isRequired(schema, key);
@@ -4532,7 +5084,7 @@ function SchemaFormInline({
4532
5084
  className: "payman-v2-ua-btn payman-v2-ua-btn-primary",
4533
5085
  disabled: locked,
4534
5086
  onClick: handleSubmit,
4535
- children: busy ? "Submitting\u2026" : isUserConfirmation ? "Confirm" : "Submit"
5087
+ children: busy ? "Submitting\u2026" : "Submit"
4536
5088
  }
4537
5089
  ),
4538
5090
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -4588,25 +5140,10 @@ function useExpiryCountdown(prompt) {
4588
5140
  const expired = initial !== void 0 && secondsLeft === 0;
4589
5141
  return [secondsLeft, expired];
4590
5142
  }
4591
- function UserActionInline({
4592
- prompt,
4593
- onSubmit,
4594
- onCancel,
4595
- onResend,
4596
- onExpired
4597
- }) {
5143
+ function UserActionInline({ prompt, onSubmit, onCancel, onResend }) {
4598
5144
  const [secondsLeft, expired] = useExpiryCountdown(prompt);
4599
- react.useEffect(() => {
4600
- if (expired && prompt.kind !== "notification") onExpired?.();
4601
- }, [expired, onExpired, prompt.kind]);
4602
5145
  let body;
4603
- if (expired && prompt.kind !== "notification") {
4604
- const note = {
4605
- id: `${prompt.userActionId}-expired`,
4606
- message: prompt.kind === "verification" ? "Verification Request Expired" : "User Form Expired"
4607
- };
4608
- body = /* @__PURE__ */ jsxRuntime.jsx(NotificationInline, { notification: note });
4609
- } else if (prompt.kind === "verification") {
5146
+ if (prompt.kind === "verification") {
4610
5147
  body = /* @__PURE__ */ jsxRuntime.jsx(
4611
5148
  VerificationInline,
4612
5149
  {
@@ -4646,23 +5183,10 @@ function UserActionInline({
4646
5183
  }
4647
5184
  var SCROLL_THRESHOLD2 = 100;
4648
5185
  var USER_SCROLL_UP_EPSILON = 4;
4649
- var PROMPT_KEY_SEPARATOR = "";
4650
- function getPromptSlotKey(prompt) {
4651
- return prompt.toolCallId || prompt.userActionId;
4652
- }
4653
- function getPromptViewKey(prompt) {
4654
- return [
4655
- getPromptSlotKey(prompt),
4656
- prompt.userActionId,
4657
- prompt.subAction ?? "",
4658
- prompt.expirySeconds ?? "",
4659
- prompt.message ?? ""
4660
- ].join(PROMPT_KEY_SEPARATOR);
4661
- }
4662
5186
  var MessageListV2 = react.forwardRef(
4663
5187
  function MessageListV22({
4664
5188
  messages,
4665
- isStreaming = false,
5189
+ isStreaming: _isStreaming = false,
4666
5190
  onEditUserMessage,
4667
5191
  onRetryUserMessage,
4668
5192
  onImageClick,
@@ -4674,10 +5198,10 @@ var MessageListV2 = react.forwardRef(
4674
5198
  onSubmitUserAction,
4675
5199
  onCancelUserAction,
4676
5200
  onResendUserAction,
4677
- onExpireUserAction,
4678
5201
  onDismissNotification,
4679
5202
  onSubmitFeedback,
4680
- typingSpeed = 4
5203
+ typingSpeed = 4,
5204
+ sidePanelOpen = false
4681
5205
  }, ref) {
4682
5206
  const noop = react.useCallback(async () => {
4683
5207
  }, []);
@@ -4685,11 +5209,11 @@ var MessageListV2 = react.forwardRef(
4685
5209
  const scrollInnerRef = react.useRef(null);
4686
5210
  const isNearBottomRef = react.useRef(true);
4687
5211
  const [showScrollBtn, setShowScrollBtn] = react.useState(false);
4688
- const [expiredPromptViewState, setExpiredPromptViewState] = react.useState({});
4689
- const expiredUserActionIdsRef = react.useRef(/* @__PURE__ */ new Set());
4690
5212
  const prevCountRef = react.useRef(messages.length);
4691
5213
  const pauseStickUntilUserMessageRef = react.useRef(false);
4692
5214
  const followingBottomRef = react.useRef(true);
5215
+ const sidePanelOpenRef = react.useRef(sidePanelOpen);
5216
+ sidePanelOpenRef.current = sidePanelOpen;
4693
5217
  const lastPinAtRef = react.useRef(0);
4694
5218
  const prevScrollTopRef = react.useRef(0);
4695
5219
  const getDistanceFromBottom = react.useCallback(() => {
@@ -4697,92 +5221,6 @@ var MessageListV2 = react.forwardRef(
4697
5221
  if (!el) return 0;
4698
5222
  return el.scrollHeight - el.scrollTop - el.clientHeight;
4699
5223
  }, []);
4700
- const messageActivityFingerprint = react.useMemo(() => {
4701
- const last = messages[messages.length - 1];
4702
- const promptFingerprint = (userActionPrompts ?? []).map((prompt) => `${getPromptViewKey(prompt)}:${prompt.status}`).join(PROMPT_KEY_SEPARATOR);
4703
- const notificationFingerprint = (notifications ?? []).map((notification) => notification.id).join(PROMPT_KEY_SEPARATOR);
4704
- if (!last) {
4705
- return [
4706
- 0,
4707
- isStreaming ? "streaming" : "idle",
4708
- promptFingerprint,
4709
- notificationFingerprint
4710
- ].join(PROMPT_KEY_SEPARATOR);
4711
- }
4712
- return [
4713
- messages.length,
4714
- isStreaming ? "streaming" : "idle",
4715
- last.id,
4716
- last.role,
4717
- last.content ?? "",
4718
- last.isStreaming ? "streaming" : "done",
4719
- last.streamProgress ?? "",
4720
- last.steps?.length ?? 0,
4721
- last.errorDetails ?? "",
4722
- promptFingerprint,
4723
- notificationFingerprint
4724
- ].join(PROMPT_KEY_SEPARATOR);
4725
- }, [isStreaming, messages, notifications, userActionPrompts]);
4726
- const handleUserActionExpired = react.useCallback(
4727
- (promptKey, userActionId) => {
4728
- setExpiredPromptViewState((prev) => {
4729
- if (prev[promptKey]) return prev;
4730
- return {
4731
- ...prev,
4732
- [promptKey]: { baseline: messageActivityFingerprint, hidden: false }
4733
- };
4734
- });
4735
- if (!expiredUserActionIdsRef.current.has(userActionId)) {
4736
- expiredUserActionIdsRef.current.add(userActionId);
4737
- void onExpireUserAction?.(userActionId)?.catch(() => {
4738
- });
4739
- }
4740
- },
4741
- [messageActivityFingerprint, onExpireUserAction]
4742
- );
4743
- react.useEffect(() => {
4744
- setExpiredPromptViewState((prev) => {
4745
- let changed = false;
4746
- const next = {};
4747
- for (const [key, state] of Object.entries(prev)) {
4748
- if (!state.hidden && state.baseline !== messageActivityFingerprint) {
4749
- next[key] = { ...state, hidden: true };
4750
- changed = true;
4751
- } else {
4752
- next[key] = state;
4753
- }
4754
- }
4755
- return changed ? next : prev;
4756
- });
4757
- }, [messageActivityFingerprint]);
4758
- react.useEffect(() => {
4759
- const livePromptKeys = new Set((userActionPrompts ?? []).map(getPromptViewKey));
4760
- const liveUserActionIds = new Set((userActionPrompts ?? []).map((p) => p.userActionId));
4761
- for (const userActionId of expiredUserActionIdsRef.current) {
4762
- if (!liveUserActionIds.has(userActionId)) {
4763
- expiredUserActionIdsRef.current.delete(userActionId);
4764
- }
4765
- }
4766
- setExpiredPromptViewState((prev) => {
4767
- let changed = false;
4768
- const next = {};
4769
- for (const [key, state] of Object.entries(prev)) {
4770
- if (livePromptKeys.has(key)) {
4771
- next[key] = state;
4772
- } else {
4773
- changed = true;
4774
- }
4775
- }
4776
- return changed ? next : prev;
4777
- });
4778
- }, [userActionPrompts]);
4779
- const visibleUserActionPrompts = react.useMemo(
4780
- () => userActionPrompts?.filter((prompt) => {
4781
- const promptKey = getPromptViewKey(prompt);
4782
- return !expiredPromptViewState[promptKey]?.hidden;
4783
- }),
4784
- [expiredPromptViewState, userActionPrompts]
4785
- );
4786
5224
  const pinToBottom = react.useCallback((behavior = "instant") => {
4787
5225
  const el = scrollRef.current;
4788
5226
  if (!el) return;
@@ -4823,17 +5261,25 @@ var MessageListV2 = react.forwardRef(
4823
5261
  const nearBottom = distance <= SCROLL_THRESHOLD2;
4824
5262
  isNearBottomRef.current = nearBottom;
4825
5263
  setShowScrollBtn(!nearBottom);
4826
- const sincePin = performance.now() - lastPinAtRef.current;
4827
- if (sincePin < 250) return;
4828
5264
  const scrolledUp = currentScrollTop < prevScrollTop - USER_SCROLL_UP_EPSILON;
4829
5265
  if (scrolledUp) {
4830
5266
  followingBottomRef.current = false;
4831
5267
  pauseStickUntilUserMessageRef.current = true;
4832
- } else if (nearBottom) {
5268
+ return;
5269
+ }
5270
+ const sincePin = performance.now() - lastPinAtRef.current;
5271
+ if (sincePin < 250) return;
5272
+ if (nearBottom) {
4833
5273
  followingBottomRef.current = true;
4834
5274
  pauseStickUntilUserMessageRef.current = false;
4835
5275
  }
4836
5276
  }, [getDistanceFromBottom]);
5277
+ const handleWheel = react.useCallback((e) => {
5278
+ if (e.deltaY < 0) {
5279
+ followingBottomRef.current = false;
5280
+ pauseStickUntilUserMessageRef.current = true;
5281
+ }
5282
+ }, []);
4837
5283
  react.useEffect(() => {
4838
5284
  const prevCount = prevCountRef.current;
4839
5285
  prevCountRef.current = messages.length;
@@ -4843,7 +5289,7 @@ var MessageListV2 = react.forwardRef(
4843
5289
  pauseStickUntilUserMessageRef.current = false;
4844
5290
  followingBottomRef.current = true;
4845
5291
  requestAnimationFrame(() => scrollToBottom());
4846
- } else if (!pauseStickUntilUserMessageRef.current && followingBottomRef.current) {
5292
+ } else if (!sidePanelOpenRef.current && !pauseStickUntilUserMessageRef.current && followingBottomRef.current) {
4847
5293
  requestAnimationFrame(() => scrollToBottom("instant"));
4848
5294
  }
4849
5295
  }
@@ -4852,27 +5298,16 @@ var MessageListV2 = react.forwardRef(
4852
5298
  const inner = scrollInnerRef.current;
4853
5299
  if (!inner) return;
4854
5300
  const pinIfFollowing = () => {
5301
+ if (sidePanelOpenRef.current) return;
4855
5302
  if (pauseStickUntilUserMessageRef.current) return;
4856
5303
  if (!followingBottomRef.current) return;
5304
+ if (getDistanceFromBottom() > SCROLL_THRESHOLD2) return;
4857
5305
  pinToBottom("instant");
4858
5306
  };
4859
- const ro = new ResizeObserver(() => {
4860
- pinIfFollowing();
4861
- });
5307
+ const ro = new ResizeObserver(pinIfFollowing);
4862
5308
  ro.observe(inner);
4863
- const mo = new MutationObserver(() => {
4864
- pinIfFollowing();
4865
- });
4866
- mo.observe(inner, {
4867
- childList: true,
4868
- subtree: true,
4869
- characterData: true
4870
- });
4871
- return () => {
4872
- ro.disconnect();
4873
- mo.disconnect();
4874
- };
4875
- }, [pinToBottom]);
5309
+ return () => ro.disconnect();
5310
+ }, [pinToBottom, getDistanceFromBottom]);
4876
5311
  react.useEffect(() => {
4877
5312
  if (messages.length > 0) {
4878
5313
  setTimeout(() => scrollToBottom("instant"), 50);
@@ -4884,6 +5319,7 @@ var MessageListV2 = react.forwardRef(
4884
5319
  {
4885
5320
  ref: scrollRef,
4886
5321
  onScroll: handleScroll,
5322
+ onWheel: handleWheel,
4887
5323
  className: "payman-v2-message-scroll payman-v2-scrollbar",
4888
5324
  children: /* @__PURE__ */ jsxRuntime.jsxs(
4889
5325
  "div",
@@ -4897,6 +5333,7 @@ var MessageListV2 = react.forwardRef(
4897
5333
  message,
4898
5334
  onEdit: onEditUserMessage,
4899
5335
  onRetry: onRetryUserMessage,
5336
+ onImageClick,
4900
5337
  retryDisabled,
4901
5338
  actions: messageActions?.userMessageActions
4902
5339
  }
@@ -4919,20 +5356,16 @@ var MessageListV2 = react.forwardRef(
4919
5356
  },
4920
5357
  note.id
4921
5358
  )),
4922
- visibleUserActionPrompts?.map((prompt) => {
4923
- const promptKey = getPromptViewKey(prompt);
4924
- return /* @__PURE__ */ jsxRuntime.jsx(
4925
- UserActionInline,
4926
- {
4927
- prompt,
4928
- onSubmit: onSubmitUserAction ?? noop,
4929
- onCancel: onCancelUserAction ?? noop,
4930
- onResend: onResendUserAction ?? noop,
4931
- onExpired: () => handleUserActionExpired(promptKey, prompt.userActionId)
4932
- },
4933
- promptKey
4934
- );
4935
- })
5359
+ userActionPrompts?.map((prompt) => /* @__PURE__ */ jsxRuntime.jsx(
5360
+ UserActionInline,
5361
+ {
5362
+ prompt,
5363
+ onSubmit: onSubmitUserAction ?? noop,
5364
+ onCancel: onCancelUserAction ?? noop,
5365
+ onResend: onResendUserAction ?? noop
5366
+ },
5367
+ prompt.toolCallId || prompt.userActionId
5368
+ ))
4936
5369
  ]
4937
5370
  }
4938
5371
  )
@@ -4962,29 +5395,146 @@ var MessageListV2 = react.forwardRef(
4962
5395
  ] });
4963
5396
  }
4964
5397
  );
4965
- var ChatInputV2 = react.forwardRef(
4966
- function ChatInputV22({
4967
- onSend,
4968
- disabled = false,
4969
- isStreaming = false,
4970
- placeholder = "Reply...",
4971
- enableVoice = false,
4972
- voiceAvailable = false,
4973
- isRecording = false,
4974
- onVoicePress,
4975
- transcribedText = "",
4976
- showResetSession = false,
4977
- onResetSession,
4978
- showAttachmentButton = true,
4979
- showUploadImageButton = true,
4980
- showAttachFileButton = true,
4981
- onUploadImageClick,
4982
- onAttachFileClick,
4983
- editingMessageId = null,
4984
- onClearEditing,
4985
- analysisMode,
4986
- onAnalysisModeChange,
4987
- slashCommands = []
5398
+ function FilePreviewModal({ src, name, onClose }) {
5399
+ const [isMounted, setIsMounted] = react.useState(false);
5400
+ const [isLoaded, setIsLoaded] = react.useState(false);
5401
+ react.useEffect(() => {
5402
+ setIsMounted(true);
5403
+ return () => setIsMounted(false);
5404
+ }, []);
5405
+ react.useEffect(() => {
5406
+ setIsLoaded(false);
5407
+ }, [src]);
5408
+ const handleKeyDown = react.useCallback(
5409
+ (e) => {
5410
+ if (e.key === "Escape") onClose();
5411
+ },
5412
+ [onClose]
5413
+ );
5414
+ react.useEffect(() => {
5415
+ if (!src || typeof document === "undefined") return;
5416
+ document.addEventListener("keydown", handleKeyDown);
5417
+ const prev = document.body.style.overflow;
5418
+ document.body.style.overflow = "hidden";
5419
+ return () => {
5420
+ document.removeEventListener("keydown", handleKeyDown);
5421
+ document.body.style.overflow = prev;
5422
+ };
5423
+ }, [src, handleKeyDown]);
5424
+ if (!isMounted || typeof document === "undefined") return null;
5425
+ return reactDom.createPortal(
5426
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: src ? /* @__PURE__ */ jsxRuntime.jsxs(
5427
+ framerMotion.motion.div,
5428
+ {
5429
+ className: "payman-v2-file-preview-overlay",
5430
+ initial: { opacity: 0 },
5431
+ animate: { opacity: 1 },
5432
+ exit: { opacity: 0 },
5433
+ transition: { duration: 0.18 },
5434
+ onClick: onClose,
5435
+ role: "dialog",
5436
+ "aria-modal": "true",
5437
+ "aria-label": `Preview: ${name}`,
5438
+ children: [
5439
+ /* @__PURE__ */ jsxRuntime.jsx(
5440
+ "button",
5441
+ {
5442
+ type: "button",
5443
+ className: "payman-v2-file-preview-close",
5444
+ "aria-label": "Close preview",
5445
+ onClick: (e) => {
5446
+ e.stopPropagation();
5447
+ onClose();
5448
+ },
5449
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 18, strokeWidth: 2 })
5450
+ }
5451
+ ),
5452
+ /* @__PURE__ */ jsxRuntime.jsx(
5453
+ framerMotion.motion.div,
5454
+ {
5455
+ className: "payman-v2-file-preview-inner",
5456
+ initial: { scale: 0.93, opacity: 0 },
5457
+ animate: { scale: isLoaded ? 1 : 0.93, opacity: isLoaded ? 1 : 0 },
5458
+ exit: { scale: 0.93, opacity: 0 },
5459
+ transition: { duration: 0.2 },
5460
+ onClick: (e) => e.stopPropagation(),
5461
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5462
+ "img",
5463
+ {
5464
+ src,
5465
+ alt: name,
5466
+ className: "payman-v2-file-preview-img",
5467
+ draggable: false,
5468
+ onLoad: () => setIsLoaded(true)
5469
+ }
5470
+ )
5471
+ }
5472
+ )
5473
+ ]
5474
+ },
5475
+ "file-preview"
5476
+ ) : null }),
5477
+ document.body
5478
+ );
5479
+ }
5480
+ function normalizeExtension(ext) {
5481
+ return ext.replace(/^\./, "").toLowerCase();
5482
+ }
5483
+ function extOf(filename) {
5484
+ return filename.split(".").pop()?.toLowerCase() ?? "";
5485
+ }
5486
+ function isAllowedImage(file, allowedExtensions) {
5487
+ const allowed = new Set(allowedExtensions.map(normalizeExtension));
5488
+ const ext = extOf(file.name);
5489
+ if (allowed.has(ext)) return true;
5490
+ if (!file.type.startsWith("image/")) return false;
5491
+ const mimeSubtype = file.type.slice("image/".length).toLowerCase();
5492
+ return allowed.has(mimeSubtype) || mimeSubtype === "jpeg" && allowed.has("jpg");
5493
+ }
5494
+ function isAllowedDocument(file, allowedExtensions) {
5495
+ const allowed = new Set(allowedExtensions.map(normalizeExtension));
5496
+ return allowed.has(extOf(file.name));
5497
+ }
5498
+ function fileTypeLabel(file, kind) {
5499
+ if (kind === "image") return "Image";
5500
+ const ext = extOf(file.name);
5501
+ return ext ? ext.toUpperCase() : "Document";
5502
+ }
5503
+ var ChatInputV2 = react.forwardRef(
5504
+ function ChatInputV22({
5505
+ onSend,
5506
+ disabled = false,
5507
+ isStreaming = false,
5508
+ isUploadingAttachments = false,
5509
+ attachmentsReady = true,
5510
+ hasAttachmentUploadErrors = false,
5511
+ attachmentUploadStatusById = {},
5512
+ uploadedAttachmentPayloads = [],
5513
+ placeholder = "Reply...",
5514
+ enableVoice = false,
5515
+ voiceAvailable = false,
5516
+ isRecording = false,
5517
+ onVoicePress,
5518
+ transcribedText = "",
5519
+ showResetSession = false,
5520
+ onResetSession,
5521
+ showAttachmentButton = true,
5522
+ showUploadImageButton = true,
5523
+ showAttachFileButton = true,
5524
+ onUploadImageClick,
5525
+ onAttachFileClick,
5526
+ allowedImageExtensions = ["png", "jpg", "jpeg", "gif", "webp"],
5527
+ allowedFileExtensions = ["pdf", "docx", "xlsx", "xls"],
5528
+ maxCount,
5529
+ maxFileBytes,
5530
+ maxTotalBytes,
5531
+ onFilesChange,
5532
+ onAttachmentsChange,
5533
+ editingMessageId = null,
5534
+ onClearEditing,
5535
+ analysisMode,
5536
+ onAnalysisModeChange,
5537
+ slashCommands = []
4988
5538
  }, ref) {
4989
5539
  const [value, setValue] = react.useState("");
4990
5540
  const [isFocused, setIsFocused] = react.useState(false);
@@ -4993,14 +5543,133 @@ var ChatInputV2 = react.forwardRef(
4993
5543
  const [selectedCommandIndex, setSelectedCommandIndex] = react.useState(0);
4994
5544
  const [inlineHint, setInlineHint] = react.useState(null);
4995
5545
  const [commandMenuDismissed, setCommandMenuDismissed] = react.useState(false);
5546
+ const [attachedFiles, setAttachedFiles] = react.useState([]);
5547
+ const [previewFile, setPreviewFile] = react.useState(null);
5548
+ const [isSending, setIsSending] = react.useState(false);
4996
5549
  const textareaRef = react.useRef(null);
4997
5550
  const actionsRef = react.useRef(null);
5551
+ const imageInputRef = react.useRef(null);
5552
+ const fileInputRef = react.useRef(null);
4998
5553
  const preRecordTextRef = react.useRef("");
4999
5554
  const voiceTooltipTimerRef = react.useRef(
5000
5555
  null
5001
5556
  );
5002
5557
  const voiceDraftSyncActiveRef = react.useRef(false);
5003
- const isInputLocked = disabled || isRecording;
5558
+ const chatContext = react.useContext(PaymanChatContext);
5559
+ react.useEffect(() => {
5560
+ return () => {
5561
+ attachedFiles.forEach((f) => URL.revokeObjectURL(f.objectUrl));
5562
+ };
5563
+ }, []);
5564
+ const notifyAttachmentList = react.useCallback(
5565
+ (files) => {
5566
+ onFilesChange?.(files.map((f) => f.file));
5567
+ onAttachmentsChange?.(files.map((f) => ({ id: f.id, file: f.file })));
5568
+ },
5569
+ [onAttachmentsChange, onFilesChange]
5570
+ );
5571
+ const clearAttachmentsFromInput = react.useCallback(() => {
5572
+ setAttachedFiles((prev) => {
5573
+ prev.forEach((f) => URL.revokeObjectURL(f.objectUrl));
5574
+ return [];
5575
+ });
5576
+ setPreviewFile(null);
5577
+ onFilesChange?.([]);
5578
+ onAttachmentsChange?.([]);
5579
+ }, [onAttachmentsChange, onFilesChange]);
5580
+ const addFiles = react.useCallback(
5581
+ (incoming, source) => {
5582
+ const isImage = source === "image";
5583
+ const allowedExtensions = isImage ? allowedImageExtensions : allowedFileExtensions;
5584
+ const isAllowed = isImage ? isAllowedImage : isAllowedDocument;
5585
+ const kindLabel = isImage ? "image" : "document";
5586
+ setAttachedFiles((prev) => {
5587
+ const accepted = [];
5588
+ let rejectedType = false;
5589
+ let hitCountLimit = false;
5590
+ let hitFileSizeLimit = false;
5591
+ let hitTotalSizeLimit = false;
5592
+ let oversizedName = "";
5593
+ const currentTotalBytes = prev.reduce(
5594
+ (sum, item) => sum + item.file.size,
5595
+ 0
5596
+ );
5597
+ let addedBytes = 0;
5598
+ for (const file of incoming) {
5599
+ if (!isAllowed(file, allowedExtensions)) {
5600
+ rejectedType = true;
5601
+ continue;
5602
+ }
5603
+ if (maxFileBytes != null && file.size > maxFileBytes) {
5604
+ hitFileSizeLimit = true;
5605
+ oversizedName = file.name;
5606
+ continue;
5607
+ }
5608
+ if (maxTotalBytes != null && currentTotalBytes + addedBytes + file.size > maxTotalBytes) {
5609
+ hitTotalSizeLimit = true;
5610
+ break;
5611
+ }
5612
+ if (maxCount != null && prev.length + accepted.length >= maxCount) {
5613
+ hitCountLimit = true;
5614
+ break;
5615
+ }
5616
+ accepted.push({
5617
+ id: `${Date.now()}-${Math.random()}`,
5618
+ file,
5619
+ kind: source,
5620
+ objectUrl: URL.createObjectURL(file)
5621
+ });
5622
+ addedBytes += file.size;
5623
+ }
5624
+ if (rejectedType) {
5625
+ setInlineHint(
5626
+ `Unsupported ${kindLabel} type. Allowed: ${allowedExtensions.map(normalizeExtension).join(", ")}.`
5627
+ );
5628
+ } else if (hitFileSizeLimit) {
5629
+ setInlineHint(
5630
+ maxFileBytes != null ? `"${oversizedName}" exceeds the ${formatAttachmentBytes(maxFileBytes)} per-file limit.` : "File exceeds the maximum allowed size."
5631
+ );
5632
+ } else if (hitTotalSizeLimit) {
5633
+ setInlineHint(
5634
+ maxTotalBytes != null ? `Total attachment size would exceed ${formatAttachmentBytes(maxTotalBytes)}.` : "Total attachment size exceeds the limit."
5635
+ );
5636
+ } else if (hitCountLimit) {
5637
+ setInlineHint(
5638
+ maxCount === 1 ? "Only 1 attachment is allowed per message." : `Maximum ${maxCount} attachments allowed per message.`
5639
+ );
5640
+ }
5641
+ if (accepted.length === 0) return prev;
5642
+ const updated = [...prev, ...accepted];
5643
+ notifyAttachmentList(updated);
5644
+ return updated;
5645
+ });
5646
+ },
5647
+ [
5648
+ allowedFileExtensions,
5649
+ allowedImageExtensions,
5650
+ maxCount,
5651
+ maxFileBytes,
5652
+ maxTotalBytes,
5653
+ notifyAttachmentList
5654
+ ]
5655
+ );
5656
+ const removeFile = react.useCallback(
5657
+ (id) => {
5658
+ setAttachedFiles((prev) => {
5659
+ const target = prev.find((f) => f.id === id);
5660
+ if (target) URL.revokeObjectURL(target.objectUrl);
5661
+ const updated = prev.filter((f) => f.id !== id);
5662
+ notifyAttachmentList(updated);
5663
+ return updated;
5664
+ });
5665
+ setPreviewFile((p) => p?.id === id ? null : p);
5666
+ },
5667
+ [notifyAttachmentList]
5668
+ );
5669
+ const imageAccept = allowedImageExtensions.map((e) => `.${e.replace(/^\./, "")}`).join(",");
5670
+ const fileAccept = allowedFileExtensions.map((e) => `.${e.replace(/^\./, "")}`).join(",");
5671
+ const isInputBusy = isSending || isUploadingAttachments;
5672
+ const isInputLocked = disabled || isRecording || isInputBusy;
5004
5673
  const hasAttachmentOptions = showUploadImageButton || showAttachFileButton;
5005
5674
  const showAttachmentMenuButton = showAttachmentButton && hasAttachmentOptions;
5006
5675
  const showVoiceButton = enableVoice && onVoicePress != null;
@@ -5040,9 +5709,10 @@ var ChatInputV2 = react.forwardRef(
5040
5709
  const end = message.length;
5041
5710
  textarea.setSelectionRange(end, end);
5042
5711
  });
5043
- }
5712
+ },
5713
+ clearAttachments: clearAttachmentsFromInput
5044
5714
  }),
5045
- [disabled]
5715
+ [disabled, clearAttachmentsFromInput]
5046
5716
  );
5047
5717
  react.useEffect(() => {
5048
5718
  if (!showActions) return;
@@ -5076,8 +5746,23 @@ var ChatInputV2 = react.forwardRef(
5076
5746
  const separator = base && !base.endsWith(" ") && transcribedText ? " " : "";
5077
5747
  setValue(`${base}${separator}${transcribedText}`);
5078
5748
  }, [isRecording, transcribedText]);
5079
- const handleSend = react.useCallback(() => {
5080
- if (!value.trim() || disabled) return;
5749
+ function uploadStatusLabel(status, file, kind) {
5750
+ if (status === "uploading") return "Uploading\u2026";
5751
+ if (status === "error") return "Upload failed";
5752
+ return fileTypeLabel(file, kind);
5753
+ }
5754
+ const handleSend = react.useCallback(async () => {
5755
+ const hasText = value.trim().length > 0;
5756
+ const hasFiles = attachedFiles.length > 0;
5757
+ if (!hasText && !hasFiles || disabled || isSending || isUploadingAttachments) return;
5758
+ if (hasFiles && !attachmentsReady) {
5759
+ setInlineHint("Waiting for attachments to finish uploading.");
5760
+ return;
5761
+ }
5762
+ if (hasFiles && hasAttachmentUploadErrors) {
5763
+ setInlineHint("Remove or re-add attachments that failed to upload.");
5764
+ return;
5765
+ }
5081
5766
  const commandHint = getSlashCommandValidationHint(value);
5082
5767
  if (commandHint) {
5083
5768
  setInlineHint(commandHint);
@@ -5087,15 +5772,38 @@ var ChatInputV2 = react.forwardRef(
5087
5772
  preRecordTextRef.current = "";
5088
5773
  setInlineHint(null);
5089
5774
  onClearEditing?.();
5090
- onSend(value.trim());
5775
+ const textToSend = value.trim();
5776
+ const filesToSend = attachedFiles.map((f) => f.file);
5777
+ const attachmentsToSend = [...uploadedAttachmentPayloads];
5091
5778
  setValue("");
5779
+ clearAttachmentsFromInput();
5092
5780
  requestAnimationFrame(() => {
5093
5781
  if (textareaRef.current) {
5094
5782
  textareaRef.current.style.height = "auto";
5095
5783
  textareaRef.current.focus();
5096
5784
  }
5097
5785
  });
5098
- }, [value, disabled, onClearEditing, onSend]);
5786
+ setIsSending(true);
5787
+ try {
5788
+ await onSend(textToSend, filesToSend, attachmentsToSend);
5789
+ } catch {
5790
+ setInlineHint("Failed to send message. Please try again.");
5791
+ } finally {
5792
+ setIsSending(false);
5793
+ }
5794
+ }, [
5795
+ value,
5796
+ attachedFiles,
5797
+ uploadedAttachmentPayloads,
5798
+ disabled,
5799
+ isSending,
5800
+ isUploadingAttachments,
5801
+ attachmentsReady,
5802
+ hasAttachmentUploadErrors,
5803
+ onClearEditing,
5804
+ onSend,
5805
+ clearAttachmentsFromInput
5806
+ ]);
5099
5807
  const selectCommand = react.useCallback(
5100
5808
  (command) => {
5101
5809
  const insertText = command.insertText ?? `${command.name} `;
@@ -5140,18 +5848,30 @@ var ChatInputV2 = react.forwardRef(
5140
5848
  }
5141
5849
  if (e.key === "Enter" && !e.shiftKey) {
5142
5850
  e.preventDefault();
5143
- if (isStreaming) return;
5144
- handleSend();
5851
+ if (isStreaming || isSending || isUploadingAttachments) return;
5852
+ void handleSend();
5145
5853
  }
5146
5854
  };
5147
5855
  const handleUploadImageClick = () => {
5856
+ imageInputRef.current?.click();
5148
5857
  onUploadImageClick?.();
5149
5858
  setShowActions(false);
5150
5859
  };
5151
5860
  const handleAttachFileClick = () => {
5861
+ fileInputRef.current?.click();
5152
5862
  onAttachFileClick?.();
5153
5863
  setShowActions(false);
5154
5864
  };
5865
+ const handleImageFilesSelected = (e) => {
5866
+ const files = Array.from(e.target.files ?? []);
5867
+ if (files.length) addFiles(files, "image");
5868
+ e.target.value = "";
5869
+ };
5870
+ const handleDocFilesSelected = (e) => {
5871
+ const files = Array.from(e.target.files ?? []);
5872
+ if (files.length) addFiles(files, "file");
5873
+ e.target.value = "";
5874
+ };
5155
5875
  const hideVoiceTooltip = react.useCallback(() => {
5156
5876
  if (voiceTooltipTimerRef.current) {
5157
5877
  clearTimeout(voiceTooltipTimerRef.current);
@@ -5179,9 +5899,39 @@ var ChatInputV2 = react.forwardRef(
5179
5899
  }
5180
5900
  onVoicePress();
5181
5901
  };
5182
- const canSend = value.trim().length > 0 && !disabled;
5183
- const sendDisabled = !canSend || isStreaming;
5902
+ const canSend = (value.trim().length > 0 || attachedFiles.length > 0) && !disabled;
5903
+ const sendDisabled = !canSend || isStreaming || isUploadingAttachments || !attachmentsReady || hasAttachmentUploadErrors || isSending;
5184
5904
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-input-container", children: [
5905
+ /* @__PURE__ */ jsxRuntime.jsx(
5906
+ "input",
5907
+ {
5908
+ ref: imageInputRef,
5909
+ type: "file",
5910
+ accept: imageAccept,
5911
+ multiple: maxCount == null || maxCount > 1,
5912
+ style: { display: "none" },
5913
+ onChange: handleImageFilesSelected
5914
+ }
5915
+ ),
5916
+ /* @__PURE__ */ jsxRuntime.jsx(
5917
+ "input",
5918
+ {
5919
+ ref: fileInputRef,
5920
+ type: "file",
5921
+ accept: fileAccept,
5922
+ multiple: maxCount == null || maxCount > 1,
5923
+ style: { display: "none" },
5924
+ onChange: handleDocFilesSelected
5925
+ }
5926
+ ),
5927
+ /* @__PURE__ */ jsxRuntime.jsx(
5928
+ FilePreviewModal,
5929
+ {
5930
+ src: previewFile?.kind === "image" ? previewFile.objectUrl : null,
5931
+ name: previewFile?.file.name ?? "",
5932
+ onClose: () => setPreviewFile(null)
5933
+ }
5934
+ ),
5185
5935
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: showCommandSuggestions && /* @__PURE__ */ jsxRuntime.jsx(
5186
5936
  framerMotion.motion.div,
5187
5937
  {
@@ -5220,6 +5970,115 @@ var ChatInputV2 = react.forwardRef(
5220
5970
  ),
5221
5971
  children: [
5222
5972
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-input-body", children: [
5973
+ /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { initial: false, children: attachedFiles.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
5974
+ framerMotion.motion.div,
5975
+ {
5976
+ initial: { opacity: 0, height: 0 },
5977
+ animate: { opacity: 1, height: "auto" },
5978
+ exit: { opacity: 0, height: 0 },
5979
+ transition: { duration: 0.15, ease: "easeOut" },
5980
+ className: "payman-v2-file-preview",
5981
+ children: attachedFiles.map((af) => {
5982
+ const uploadStatus = attachmentUploadStatusById[af.id];
5983
+ const isUploadingFile = uploadStatus === "uploading";
5984
+ const hasUploadError = uploadStatus === "error";
5985
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-file-preview-item", children: [
5986
+ af.kind === "image" ? /* @__PURE__ */ jsxRuntime.jsxs(
5987
+ "button",
5988
+ {
5989
+ type: "button",
5990
+ className: cn(
5991
+ "payman-v2-file-preview-shell payman-v2-file-preview-shell-clickable",
5992
+ hasUploadError && "payman-v2-file-preview-shell-error"
5993
+ ),
5994
+ onClick: () => setPreviewFile(af),
5995
+ disabled: isUploadingFile,
5996
+ "aria-label": `Preview ${af.file.name}`,
5997
+ children: [
5998
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-file-preview-thumb", children: [
5999
+ /* @__PURE__ */ jsxRuntime.jsx(
6000
+ "img",
6001
+ {
6002
+ src: af.objectUrl,
6003
+ alt: "",
6004
+ draggable: false
6005
+ }
6006
+ ),
6007
+ isUploadingFile && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-file-preview-uploading", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 14, className: "payman-v2-spin" }) })
6008
+ ] }),
6009
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-file-preview-info", children: [
6010
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-file-preview-name", children: af.file.name }),
6011
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-file-preview-type", children: uploadStatusLabel(uploadStatus, af.file, af.kind) })
6012
+ ] })
6013
+ ]
6014
+ }
6015
+ ) : isPdfFile(af.file) ? /* @__PURE__ */ jsxRuntime.jsxs(
6016
+ "button",
6017
+ {
6018
+ type: "button",
6019
+ className: cn(
6020
+ "payman-v2-file-preview-shell payman-v2-file-preview-shell-clickable",
6021
+ hasUploadError && "payman-v2-file-preview-shell-error"
6022
+ ),
6023
+ onClick: () => chatContext?.openPdfSheet(
6024
+ af.objectUrl,
6025
+ af.file.name
6026
+ ),
6027
+ disabled: isUploadingFile,
6028
+ "aria-label": `Preview ${af.file.name}`,
6029
+ children: [
6030
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-file-preview-thumb", children: isUploadingFile ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "payman-v2-spin payman-v2-file-preview-doc-icon" }) : /* @__PURE__ */ jsxRuntime.jsx(
6031
+ lucideReact.FileText,
6032
+ {
6033
+ size: 16,
6034
+ strokeWidth: 1.75,
6035
+ className: "payman-v2-file-preview-doc-icon"
6036
+ }
6037
+ ) }),
6038
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-file-preview-info", children: [
6039
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-file-preview-name", children: af.file.name }),
6040
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-file-preview-type", children: uploadStatusLabel(uploadStatus, af.file, af.kind) })
6041
+ ] })
6042
+ ]
6043
+ }
6044
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
6045
+ "div",
6046
+ {
6047
+ className: cn(
6048
+ "payman-v2-file-preview-shell",
6049
+ hasUploadError && "payman-v2-file-preview-shell-error"
6050
+ ),
6051
+ children: [
6052
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-file-preview-thumb", children: isUploadingFile ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 16, className: "payman-v2-spin payman-v2-file-preview-doc-icon" }) : /* @__PURE__ */ jsxRuntime.jsx(
6053
+ lucideReact.FileText,
6054
+ {
6055
+ size: 16,
6056
+ strokeWidth: 1.75,
6057
+ className: "payman-v2-file-preview-doc-icon"
6058
+ }
6059
+ ) }),
6060
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-file-preview-info", children: [
6061
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-file-preview-name", children: af.file.name }),
6062
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-file-preview-type", children: uploadStatusLabel(uploadStatus, af.file, af.kind) })
6063
+ ] })
6064
+ ]
6065
+ }
6066
+ ),
6067
+ /* @__PURE__ */ jsxRuntime.jsx(
6068
+ "button",
6069
+ {
6070
+ type: "button",
6071
+ className: "payman-v2-file-preview-remove",
6072
+ onClick: () => removeFile(af.id),
6073
+ "aria-label": `Remove ${af.file.name}`,
6074
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 12, strokeWidth: 2.5 })
6075
+ }
6076
+ )
6077
+ ] }, af.id);
6078
+ })
6079
+ },
6080
+ "file-preview"
6081
+ ) }),
5223
6082
  /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { initial: false, children: editingMessageId && /* @__PURE__ */ jsxRuntime.jsxs(
5224
6083
  framerMotion.motion.div,
5225
6084
  {
@@ -5451,13 +6310,13 @@ var ChatInputV2 = react.forwardRef(
5451
6310
  "button",
5452
6311
  {
5453
6312
  type: "button",
5454
- onClick: handleSend,
6313
+ onClick: () => void handleSend(),
5455
6314
  disabled: sendDisabled,
5456
6315
  className: cn(
5457
6316
  "payman-v2-input-send-btn",
5458
6317
  sendDisabled && "payman-v2-input-send-btn-disabled"
5459
6318
  ),
5460
- "aria-label": "Send message",
6319
+ "aria-label": isUploadingAttachments || isSending ? "Uploading attachments" : "Send message",
5461
6320
  children: /* @__PURE__ */ jsxRuntime.jsx(
5462
6321
  lucideReact.ArrowUp,
5463
6322
  {
@@ -5992,6 +6851,389 @@ function TimelineBars({
5992
6851
  )
5993
6852
  ] });
5994
6853
  }
6854
+
6855
+ // src/utils/pdfPreview.ts
6856
+ var PdfPreviewError = class extends Error {
6857
+ constructor(kind, title, userMessage) {
6858
+ super(userMessage);
6859
+ __publicField(this, "kind");
6860
+ __publicField(this, "title");
6861
+ __publicField(this, "userMessage");
6862
+ this.name = "PdfPreviewError";
6863
+ this.kind = kind;
6864
+ this.title = title;
6865
+ this.userMessage = userMessage;
6866
+ }
6867
+ };
6868
+ function classifyErrorBody(body, status) {
6869
+ const text = body.replace(/\s+/g, " ").trim();
6870
+ if (/signed expiry time|must be after signed start time|expired|expir/i.test(text)) {
6871
+ return new PdfPreviewError(
6872
+ "expired",
6873
+ "Link expired",
6874
+ "This download link has expired. Ask the assistant to generate a new copy of the document."
6875
+ );
6876
+ }
6877
+ if (/AuthenticationFailed|authorization header|formed correctly including the signature/i.test(
6878
+ text
6879
+ )) {
6880
+ return new PdfPreviewError(
6881
+ "forbidden",
6882
+ "Link no longer valid",
6883
+ "This preview link is no longer valid. Request a fresh download link and try again."
6884
+ );
6885
+ }
6886
+ if (status === 404 || /BlobNotFound|ResourceNotFound|The specified blob does not exist/i.test(text)) {
6887
+ return new PdfPreviewError(
6888
+ "not_found",
6889
+ "Document not found",
6890
+ "The file is no longer available. It may have been removed or the link is incorrect."
6891
+ );
6892
+ }
6893
+ if (status === 403) {
6894
+ return new PdfPreviewError(
6895
+ "forbidden",
6896
+ "Can't open document",
6897
+ "You may not have access to this file, or the link has expired."
6898
+ );
6899
+ }
6900
+ if (status === 401) {
6901
+ return new PdfPreviewError(
6902
+ "forbidden",
6903
+ "Access denied",
6904
+ "This preview link could not be verified. Request a new download link."
6905
+ );
6906
+ }
6907
+ return new PdfPreviewError(
6908
+ "unknown",
6909
+ "Can't preview document",
6910
+ "Something went wrong while opening this file. Try downloading it or request a new link."
6911
+ );
6912
+ }
6913
+ function normalizePdfPreviewError(error) {
6914
+ if (error instanceof PdfPreviewError) return error;
6915
+ if (error instanceof TypeError) {
6916
+ return new PdfPreviewError(
6917
+ "network",
6918
+ "Connection problem",
6919
+ "Could not load the document. Check your connection and try again."
6920
+ );
6921
+ }
6922
+ return new PdfPreviewError(
6923
+ "unknown",
6924
+ "Can't preview document",
6925
+ "Something went wrong while opening this file. Try downloading it or request a new link."
6926
+ );
6927
+ }
6928
+ async function assertPdfBlob(blob, status) {
6929
+ if (!blob.size) {
6930
+ return Promise.reject(
6931
+ new PdfPreviewError(
6932
+ "empty",
6933
+ "Document unavailable",
6934
+ "The file response was empty. Request a new download link and try again."
6935
+ )
6936
+ );
6937
+ }
6938
+ const head = await blob.slice(0, Math.min(blob.size, 1024)).text();
6939
+ if (head.startsWith("%PDF")) {
6940
+ return blob;
6941
+ }
6942
+ throw classifyErrorBody(head, status);
6943
+ }
6944
+ async function fetchPdfBlob(src) {
6945
+ let response;
6946
+ try {
6947
+ response = await fetch(src, {
6948
+ method: "GET",
6949
+ headers: { Accept: "application/pdf,*/*" }
6950
+ });
6951
+ } catch {
6952
+ throw new PdfPreviewError(
6953
+ "network",
6954
+ "Connection problem",
6955
+ "Could not load the document. Check your connection and try again."
6956
+ );
6957
+ }
6958
+ const blob = await response.blob();
6959
+ if (!response.ok) {
6960
+ const body = await blob.text().catch(() => "");
6961
+ throw classifyErrorBody(body, response.status);
6962
+ }
6963
+ return assertPdfBlob(blob, response.status);
6964
+ }
6965
+ var MIN_WIDTH = 320;
6966
+ var MAX_WIDTH_RATIO = 0.6;
6967
+ var DEFAULT_WIDTH = 520;
6968
+ var SPRING = { type: "spring", stiffness: 340, damping: 34, mass: 0.85 };
6969
+ var SHEET_EXIT = { duration: 0.22, ease: [0.4, 0, 1, 1] };
6970
+ function pdfDownloadName(title) {
6971
+ const base = title.trim() || "document";
6972
+ return base.toLowerCase().endsWith(".pdf") ? base : `${base}.pdf`;
6973
+ }
6974
+ function clampSplitWidth(w) {
6975
+ const maxW = Math.floor(window.innerWidth * MAX_WIDTH_RATIO);
6976
+ return Math.max(MIN_WIDTH, Math.min(maxW, w));
6977
+ }
6978
+ function PdfSheetV2({
6979
+ src,
6980
+ title,
6981
+ onClose,
6982
+ mode = "split"
6983
+ }) {
6984
+ return /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { children: src && /* @__PURE__ */ jsxRuntime.jsx(
6985
+ PdfSheetPanel,
6986
+ {
6987
+ src,
6988
+ title,
6989
+ onClose,
6990
+ mode
6991
+ },
6992
+ "pdf-panel"
6993
+ ) });
6994
+ }
6995
+ function PdfSheetPanel({
6996
+ src,
6997
+ title,
6998
+ onClose,
6999
+ mode
7000
+ }) {
7001
+ const isSheet = mode === "sheet";
7002
+ const [isLoaded, setIsLoaded] = react.useState(false);
7003
+ const [isDownloading, setIsDownloading] = react.useState(false);
7004
+ const [previewUrl, setPreviewUrl] = react.useState(null);
7005
+ const [previewError, setPreviewError] = react.useState(null);
7006
+ const [loadAttempt, setLoadAttempt] = react.useState(0);
7007
+ const blobRef = react.useRef(null);
7008
+ const objectUrlRef = react.useRef(null);
7009
+ const [panelWidth, setPanelWidth] = react.useState(DEFAULT_WIDTH);
7010
+ const [splitOpened, setSplitOpened] = react.useState(false);
7011
+ const [isDragging, setIsDragging] = react.useState(false);
7012
+ react.useEffect(() => {
7013
+ setIsLoaded(false);
7014
+ setPreviewUrl(null);
7015
+ setPreviewError(null);
7016
+ setPanelWidth(DEFAULT_WIDTH);
7017
+ setSplitOpened(false);
7018
+ blobRef.current = null;
7019
+ if (objectUrlRef.current) {
7020
+ URL.revokeObjectURL(objectUrlRef.current);
7021
+ objectUrlRef.current = null;
7022
+ }
7023
+ let cancelled = false;
7024
+ const loadPreview = async () => {
7025
+ try {
7026
+ const blob = await fetchPdfBlob(src);
7027
+ if (cancelled) return;
7028
+ blobRef.current = blob;
7029
+ const objectUrl = URL.createObjectURL(blob);
7030
+ objectUrlRef.current = objectUrl;
7031
+ setPreviewUrl(objectUrl);
7032
+ } catch (error) {
7033
+ if (!cancelled) {
7034
+ setPreviewError(normalizePdfPreviewError(error));
7035
+ }
7036
+ }
7037
+ };
7038
+ void loadPreview();
7039
+ return () => {
7040
+ cancelled = true;
7041
+ if (objectUrlRef.current) {
7042
+ URL.revokeObjectURL(objectUrlRef.current);
7043
+ objectUrlRef.current = null;
7044
+ }
7045
+ blobRef.current = null;
7046
+ };
7047
+ }, [src, loadAttempt]);
7048
+ react.useEffect(() => {
7049
+ if (isSheet) return;
7050
+ const onWindowResize = () => setPanelWidth((size) => clampSplitWidth(size));
7051
+ window.addEventListener("resize", onWindowResize);
7052
+ return () => window.removeEventListener("resize", onWindowResize);
7053
+ }, [isSheet]);
7054
+ const handleKeyDown = react.useCallback(
7055
+ (e) => {
7056
+ if (e.key === "Escape") onClose();
7057
+ },
7058
+ [onClose]
7059
+ );
7060
+ react.useEffect(() => {
7061
+ document.addEventListener("keydown", handleKeyDown);
7062
+ return () => document.removeEventListener("keydown", handleKeyDown);
7063
+ }, [handleKeyDown]);
7064
+ const canDownload = isLoaded && previewUrl != null && !previewError;
7065
+ const handleDownload = async () => {
7066
+ const filename = pdfDownloadName(title);
7067
+ setIsDownloading(true);
7068
+ try {
7069
+ const blob = blobRef.current ?? await fetchPdfBlob(src);
7070
+ const objectUrl = URL.createObjectURL(blob);
7071
+ const a = document.createElement("a");
7072
+ a.href = objectUrl;
7073
+ a.download = filename;
7074
+ a.target = "_blank";
7075
+ a.rel = "noopener noreferrer";
7076
+ document.body.appendChild(a);
7077
+ a.click();
7078
+ a.remove();
7079
+ URL.revokeObjectURL(objectUrl);
7080
+ } catch (error) {
7081
+ setPreviewError(normalizePdfPreviewError(error));
7082
+ } finally {
7083
+ setIsDownloading(false);
7084
+ }
7085
+ };
7086
+ const handleRetry = () => {
7087
+ setPreviewError(null);
7088
+ setIsLoaded(false);
7089
+ setPreviewUrl(null);
7090
+ setLoadAttempt((attempt) => attempt + 1);
7091
+ };
7092
+ const handleOpenInNewTab = () => {
7093
+ window.open(src, "_blank", "noopener,noreferrer");
7094
+ };
7095
+ const onResizeMouseDown = (e) => {
7096
+ if (e.button !== 0) return;
7097
+ e.preventDefault();
7098
+ const startX = e.clientX;
7099
+ const startW = panelWidth;
7100
+ setIsDragging(true);
7101
+ const onMove = (ev) => {
7102
+ setPanelWidth(clampSplitWidth(startW + (startX - ev.clientX)));
7103
+ };
7104
+ const onUp = () => {
7105
+ setIsDragging(false);
7106
+ document.removeEventListener("mousemove", onMove);
7107
+ document.removeEventListener("mouseup", onUp);
7108
+ };
7109
+ document.addEventListener("mousemove", onMove);
7110
+ document.addEventListener("mouseup", onUp);
7111
+ };
7112
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7113
+ framerMotion.motion.div,
7114
+ {
7115
+ className: cn(
7116
+ "payman-v2-root payman-v2-pdf-sheet-panel",
7117
+ isSheet && "payman-v2-pdf-sheet-panel--sheet"
7118
+ ),
7119
+ style: { minWidth: 0 },
7120
+ initial: isSheet ? { x: "100%", opacity: 0 } : { width: 0, opacity: 0 },
7121
+ animate: isSheet ? { x: 0, opacity: 1 } : { width: panelWidth, opacity: 1 },
7122
+ exit: isSheet ? { x: "100%", opacity: 0, transition: { x: SHEET_EXIT, opacity: SHEET_EXIT } } : { width: 0, opacity: 0, transition: { width: SHEET_EXIT, opacity: SHEET_EXIT } },
7123
+ transition: isSheet ? { x: SPRING, opacity: SPRING } : {
7124
+ width: splitOpened ? { duration: 0 } : SPRING,
7125
+ opacity: SPRING
7126
+ },
7127
+ onAnimationComplete: () => {
7128
+ if (!isSheet && !splitOpened) setSplitOpened(true);
7129
+ },
7130
+ children: [
7131
+ !isSheet && /* @__PURE__ */ jsxRuntime.jsx(
7132
+ "div",
7133
+ {
7134
+ className: "payman-v2-pdf-sheet-resize-handle",
7135
+ onMouseDown: onResizeMouseDown,
7136
+ "aria-hidden": "true",
7137
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-pdf-sheet-resize-grip" })
7138
+ }
7139
+ ),
7140
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-pdf-sheet-header", children: [
7141
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-pdf-sheet-header-left", children: [
7142
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-pdf-sheet-file-icon", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { size: 14, strokeWidth: 1.75 }) }),
7143
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "payman-v2-pdf-sheet-title", title, children: title || "Document" })
7144
+ ] }),
7145
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-pdf-sheet-header-actions", children: [
7146
+ /* @__PURE__ */ jsxRuntime.jsxs(
7147
+ "button",
7148
+ {
7149
+ type: "button",
7150
+ className: "payman-v2-pdf-sheet-download-btn",
7151
+ "aria-label": "Download PDF",
7152
+ disabled: !canDownload || isDownloading,
7153
+ onClick: () => void handleDownload(),
7154
+ children: [
7155
+ isDownloading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 13, strokeWidth: 2, style: { animation: "payman-v2-spin 0.65s linear infinite" } }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 13, strokeWidth: 2 }),
7156
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: isDownloading ? "Downloading\u2026" : "Download" })
7157
+ ]
7158
+ }
7159
+ ),
7160
+ /* @__PURE__ */ jsxRuntime.jsx(
7161
+ "button",
7162
+ {
7163
+ type: "button",
7164
+ className: "payman-v2-pdf-sheet-close-btn",
7165
+ "aria-label": "Close preview",
7166
+ onClick: (e) => {
7167
+ e.stopPropagation();
7168
+ onClose();
7169
+ },
7170
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 15, strokeWidth: 2.25 })
7171
+ }
7172
+ )
7173
+ ] })
7174
+ ] }),
7175
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-pdf-sheet-body", children: previewError ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-pdf-sheet-error", role: "alert", children: [
7176
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-pdf-sheet-error-icon", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { size: 22, strokeWidth: 1.75 }) }),
7177
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-pdf-sheet-error-title", children: previewError.title }),
7178
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-pdf-sheet-error-message", children: previewError.userMessage }),
7179
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-pdf-sheet-error-actions", children: [
7180
+ previewError.kind === "network" && /* @__PURE__ */ jsxRuntime.jsxs(
7181
+ "button",
7182
+ {
7183
+ type: "button",
7184
+ className: "payman-v2-pdf-sheet-error-btn",
7185
+ onClick: handleRetry,
7186
+ children: [
7187
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 14, strokeWidth: 2 }),
7188
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Try again" })
7189
+ ]
7190
+ }
7191
+ ),
7192
+ (previewError.kind === "network" || previewError.kind === "unknown") && /* @__PURE__ */ jsxRuntime.jsxs(
7193
+ "button",
7194
+ {
7195
+ type: "button",
7196
+ className: "payman-v2-pdf-sheet-error-btn payman-v2-pdf-sheet-error-btn-secondary",
7197
+ onClick: handleOpenInNewTab,
7198
+ children: [
7199
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { size: 14, strokeWidth: 2 }),
7200
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Open link" })
7201
+ ]
7202
+ }
7203
+ )
7204
+ ] })
7205
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7206
+ (!isLoaded || !previewUrl) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "payman-v2-pdf-sheet-loading", children: [
7207
+ /* @__PURE__ */ jsxRuntime.jsx(
7208
+ lucideReact.Loader2,
7209
+ {
7210
+ size: 20,
7211
+ strokeWidth: 2,
7212
+ style: { animation: "payman-v2-spin 0.65s linear infinite", color: "var(--payman-v2-text-3)" }
7213
+ }
7214
+ ),
7215
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "payman-v2-pdf-sheet-loading-text", children: previewUrl ? "Preparing preview\u2026" : "Opening document\u2026" })
7216
+ ] }),
7217
+ previewUrl && /* @__PURE__ */ jsxRuntime.jsx(
7218
+ "iframe",
7219
+ {
7220
+ src: previewUrl,
7221
+ title: title || "PDF Preview",
7222
+ className: "payman-v2-pdf-sheet-iframe",
7223
+ style: {
7224
+ opacity: isLoaded ? 1 : 0,
7225
+ transition: "opacity 0.3s ease",
7226
+ pointerEvents: isDragging ? "none" : "auto"
7227
+ },
7228
+ onLoad: () => setIsLoaded(true)
7229
+ },
7230
+ previewUrl
7231
+ )
7232
+ ] }) })
7233
+ ]
7234
+ }
7235
+ );
7236
+ }
5995
7237
  var DEFAULT_USER_ACTION_STATE = {
5996
7238
  prompts: [],
5997
7239
  notifications: []
@@ -6086,7 +7328,8 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6086
7328
  onLoadMoreMessages,
6087
7329
  isLoadingMoreMessages = false,
6088
7330
  hasMoreMessages = false,
6089
- chat
7331
+ chat,
7332
+ attachmentUpload
6090
7333
  }, ref) {
6091
7334
  const [inputValue, setInputValue] = react.useState("");
6092
7335
  const prevInputValueRef = react.useRef(inputValue);
@@ -6140,7 +7383,6 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6140
7383
  const submitUserAction2 = chat.submitUserAction ?? NOOP_ASYNC;
6141
7384
  const cancelUserAction2 = chat.cancelUserAction ?? NOOP_ASYNC;
6142
7385
  const resendUserAction2 = chat.resendUserAction ?? NOOP_ASYNC;
6143
- const expireUserAction2 = chat.expireUserAction ?? NOOP_ASYNC;
6144
7386
  const dismissNotification = chat.dismissNotification ?? NOOP;
6145
7387
  const isUserActionSupported = typeof chat.submitUserAction === "function" && typeof chat.cancelUserAction === "function" && typeof chat.resendUserAction === "function";
6146
7388
  const {
@@ -6166,6 +7408,19 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6166
7408
  }
6167
7409
  }
6168
7410
  );
7411
+ const [pdfSheet, setPdfSheet] = react.useState(null);
7412
+ const autoOpenedPdfHrefsRef = react.useRef(/* @__PURE__ */ new Set());
7413
+ const pdfPreviewMode = config.pdfPreviewMode ?? "split";
7414
+ const openPdfSheet = react.useCallback((href, title, options) => {
7415
+ if (options?.auto) {
7416
+ if (autoOpenedPdfHrefsRef.current.has(href)) return;
7417
+ autoOpenedPdfHrefsRef.current.add(href);
7418
+ }
7419
+ setPdfSheet({ href, title });
7420
+ }, []);
7421
+ const closePdfSheet = react.useCallback(() => {
7422
+ setPdfSheet(null);
7423
+ }, []);
6169
7424
  const contextValue = react.useMemo(
6170
7425
  () => ({
6171
7426
  resetSession,
@@ -6174,7 +7429,8 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6174
7429
  cancelStream,
6175
7430
  getSessionId,
6176
7431
  getMessages,
6177
- isWaitingForResponse
7432
+ isWaitingForResponse,
7433
+ openPdfSheet
6178
7434
  }),
6179
7435
  [
6180
7436
  resetSession,
@@ -6183,7 +7439,8 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6183
7439
  cancelStream,
6184
7440
  getSessionId,
6185
7441
  getMessages,
6186
- isWaitingForResponse
7442
+ isWaitingForResponse,
7443
+ openPdfSheet
6187
7444
  ]
6188
7445
  );
6189
7446
  const {
@@ -6239,7 +7496,10 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6239
7496
  setInputValue("");
6240
7497
  setLightboxSrc(null);
6241
7498
  setLightboxAlt("");
7499
+ setPdfSheet(null);
7500
+ autoOpenedPdfHrefsRef.current.clear();
6242
7501
  chatInputV2Ref.current?.setDraft("");
7502
+ chatInputV2Ref.current?.clearAttachments();
6243
7503
  clearTranscript();
6244
7504
  if (isRecording) {
6245
7505
  stopRecording();
@@ -6290,14 +7550,20 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6290
7550
  emptyStateComponent,
6291
7551
  showResetSession = false,
6292
7552
  enableDeepModeToggle = true,
6293
- showAttachmentButton = true,
6294
- showUploadImageButton = true,
6295
- showAttachFileButton = true,
6296
7553
  messageActions: messageActionsConfig,
6297
7554
  enableSlashCommands = true,
6298
7555
  slashCommands: slashCommandsConfig,
6299
7556
  commandPermissions
6300
7557
  } = config;
7558
+ const attachmentSettings = react.useMemo(
7559
+ () => resolveChatAttachmentConfig(config),
7560
+ [
7561
+ config.attachments,
7562
+ config.showAttachmentButton,
7563
+ config.showUploadImageButton,
7564
+ config.showAttachFileButton
7565
+ ]
7566
+ );
6301
7567
  const messageActions = react.useMemo(
6302
7568
  () => ({
6303
7569
  userMessageActions: {
@@ -6385,11 +7651,22 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6385
7651
  };
6386
7652
  const userActionPrompts = isUserActionSupported ? userActionState.prompts : void 0;
6387
7653
  const notifications = userActionState.notifications;
6388
- const handleV2Send = (text) => {
7654
+ const handleAttachmentsChange = react.useCallback(
7655
+ (attachments) => {
7656
+ attachmentUpload.syncAttachments(attachments);
7657
+ },
7658
+ [attachmentUpload]
7659
+ );
7660
+ const handleV2Send = (text, files = [], attachments = []) => {
6389
7661
  if (isRecording) stopRecording();
6390
- if (text.trim() && !disableInput && isSessionParamsConfigured) {
7662
+ if ((text.trim() || files.length > 0) && !disableInput && isSessionParamsConfigured) {
7663
+ if (files.length > 0 && attachments.length === 0) return;
6391
7664
  setEditingMessageId(null);
6392
- void sendMessage(text.trim(), { analysisMode: effectiveAnalysisMode });
7665
+ void sendMessage(text.trim(), {
7666
+ analysisMode: effectiveAnalysisMode,
7667
+ attachments,
7668
+ files
7669
+ });
6393
7670
  }
6394
7671
  };
6395
7672
  const handleVoicePress = react.useCallback(async () => {
@@ -6437,156 +7714,214 @@ var PaymanChatInner = react.forwardRef(function PaymanChatInner2({
6437
7714
  style,
6438
7715
  children: [
6439
7716
  children,
6440
- /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: isEmpty && !hasEverSentMessage ? /* @__PURE__ */ jsxRuntime.jsx(
6441
- framerMotion.motion.div,
7717
+ /* @__PURE__ */ jsxRuntime.jsxs(
7718
+ "div",
6442
7719
  {
6443
- initial: { opacity: 1 },
6444
- exit: { opacity: 0 },
6445
- transition: { duration: 0.3 },
6446
- className: "payman-v2-chat-layout",
6447
- style: { justifyContent: "center", alignItems: "center" },
6448
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", flex: 1, width: "100%" }, children: [
6449
- /* @__PURE__ */ jsxRuntime.jsx(
6450
- MessageList,
6451
- {
6452
- messages,
6453
- isLoading: false,
6454
- emptyStateText,
6455
- showEmptyStateIcon,
6456
- emptyStateComponent,
6457
- layout,
6458
- showTimestamps,
6459
- stage: config.stage || "DEVELOPMENT",
6460
- animated,
6461
- showAgentName,
6462
- agentName,
6463
- showAvatars,
6464
- showUserAvatar,
6465
- showAssistantAvatar,
6466
- showExecutionSteps,
6467
- showStreamingDot,
6468
- streamingStepsText,
6469
- completedStepsText,
6470
- onExecutionTraceClick,
6471
- onLoadMoreMessages,
6472
- isLoadingMoreMessages,
6473
- hasMoreMessages
6474
- }
6475
- ),
6476
- /* @__PURE__ */ jsxRuntime.jsx(
7720
+ className: cn(
7721
+ "payman-v2-split-layout",
7722
+ pdfPreviewMode === "sheet" && "payman-v2-split-layout--sheet"
7723
+ ),
7724
+ children: [
7725
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "payman-v2-chat-column", children: /* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "wait", children: isEmpty && !hasEverSentMessage ? /* @__PURE__ */ jsxRuntime.jsx(
6477
7726
  framerMotion.motion.div,
6478
7727
  {
6479
- initial: { opacity: 0, y: 12 },
6480
- animate: { opacity: 1, y: 0 },
6481
- transition: { delay: 0.2, duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] },
6482
- style: { width: "100%" },
6483
- children: hasAskPermission && /* @__PURE__ */ jsxRuntime.jsx(
6484
- ChatInputV2,
6485
- {
6486
- ref: chatInputV2Ref,
6487
- onSend: handleV2Send,
6488
- onCancel: cancelStream,
6489
- disabled: isV2InputDisabled,
6490
- isStreaming: isWaitingForResponse,
6491
- placeholder: isRecording ? "Listening..." : placeholder,
6492
- enableVoice: config.enableVoice === true,
6493
- transcribedText: config.enableVoice === true ? transcribedText : "",
6494
- voiceAvailable: config.enableVoice === true && voiceAvailable,
6495
- isRecording,
6496
- onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
6497
- onCancelRecording: handleCancelRecording,
6498
- onConfirmRecording: handleConfirmRecording,
6499
- showResetSession,
6500
- onResetSession: requestResetSession,
6501
- showAttachmentButton,
6502
- showUploadImageButton,
6503
- showAttachFileButton,
6504
- onUploadImageClick,
6505
- onAttachFileClick,
6506
- editingMessageId,
6507
- onClearEditing: handleClearEditing,
6508
- analysisMode: enableDeepModeToggle ? analysisMode : void 0,
6509
- onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
6510
- slashCommands
6511
- }
6512
- )
6513
- }
6514
- )
6515
- ] })
6516
- },
6517
- "v2-empty"
6518
- ) : /* @__PURE__ */ jsxRuntime.jsxs(
6519
- framerMotion.motion.div,
6520
- {
6521
- initial: hasEverSentMessage ? { opacity: 0 } : false,
6522
- animate: { opacity: 1 },
6523
- transition: { duration: 0.3 },
6524
- className: "payman-v2-chat-layout",
6525
- children: [
6526
- /* @__PURE__ */ jsxRuntime.jsx(
6527
- MessageListV2,
7728
+ initial: { opacity: 1 },
7729
+ exit: { opacity: 0 },
7730
+ transition: { duration: 0.3 },
7731
+ className: "payman-v2-chat-layout",
7732
+ style: { justifyContent: "center", alignItems: "center" },
7733
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", flex: 1, width: "100%" }, children: [
7734
+ /* @__PURE__ */ jsxRuntime.jsx(
7735
+ MessageList,
7736
+ {
7737
+ messages,
7738
+ isLoading: false,
7739
+ emptyStateText,
7740
+ showEmptyStateIcon,
7741
+ emptyStateComponent,
7742
+ layout,
7743
+ showTimestamps,
7744
+ stage: config.stage || "DEVELOPMENT",
7745
+ animated,
7746
+ showAgentName,
7747
+ agentName,
7748
+ showAvatars,
7749
+ showUserAvatar,
7750
+ showAssistantAvatar,
7751
+ showExecutionSteps,
7752
+ showStreamingDot,
7753
+ streamingStepsText,
7754
+ completedStepsText,
7755
+ onExecutionTraceClick,
7756
+ onLoadMoreMessages,
7757
+ isLoadingMoreMessages,
7758
+ hasMoreMessages
7759
+ }
7760
+ ),
7761
+ /* @__PURE__ */ jsxRuntime.jsx(
7762
+ framerMotion.motion.div,
7763
+ {
7764
+ initial: { opacity: 0, y: 12 },
7765
+ animate: { opacity: 1, y: 0 },
7766
+ transition: { delay: 0.2, duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] },
7767
+ style: { width: "100%" },
7768
+ children: hasAskPermission && /* @__PURE__ */ jsxRuntime.jsx(
7769
+ ChatInputV2,
7770
+ {
7771
+ ref: chatInputV2Ref,
7772
+ onSend: handleV2Send,
7773
+ onCancel: cancelStream,
7774
+ disabled: isV2InputDisabled,
7775
+ isStreaming: isWaitingForResponse,
7776
+ isUploadingAttachments: attachmentUpload.isUploading,
7777
+ attachmentsReady: attachmentUpload.allReady,
7778
+ hasAttachmentUploadErrors: attachmentUpload.hasErrors,
7779
+ attachmentUploadStatusById: attachmentUpload.statusById,
7780
+ uploadedAttachmentPayloads: attachmentUpload.payloads,
7781
+ onAttachmentsChange: handleAttachmentsChange,
7782
+ placeholder: isRecording ? "Listening..." : placeholder,
7783
+ enableVoice: config.enableVoice === true,
7784
+ transcribedText: config.enableVoice === true ? transcribedText : "",
7785
+ voiceAvailable: config.enableVoice === true && voiceAvailable,
7786
+ isRecording,
7787
+ onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
7788
+ onCancelRecording: handleCancelRecording,
7789
+ onConfirmRecording: handleConfirmRecording,
7790
+ showResetSession,
7791
+ onResetSession: requestResetSession,
7792
+ showAttachmentButton: attachmentSettings.showAttachmentButton,
7793
+ showUploadImageButton: attachmentSettings.showUploadImageButton,
7794
+ showAttachFileButton: attachmentSettings.showAttachFileButton,
7795
+ allowedImageExtensions: attachmentSettings.allowedImageExtensions,
7796
+ allowedFileExtensions: attachmentSettings.allowedFileExtensions,
7797
+ maxCount: attachmentSettings.maxCount,
7798
+ maxFileBytes: attachmentSettings.maxFileBytes,
7799
+ maxTotalBytes: attachmentSettings.maxTotalBytes,
7800
+ onUploadImageClick,
7801
+ onAttachFileClick,
7802
+ editingMessageId,
7803
+ onClearEditing: handleClearEditing,
7804
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
7805
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
7806
+ slashCommands
7807
+ }
7808
+ )
7809
+ }
7810
+ )
7811
+ ] })
7812
+ },
7813
+ "v2-empty"
7814
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
7815
+ framerMotion.motion.div,
6528
7816
  {
6529
- ref: messageListV2Ref,
6530
- messages,
6531
- isStreaming: isWaitingForResponse,
6532
- onEditUserMessage: handleEditMessageDraft,
6533
- onRetryUserMessage: handleRetryUserMessage,
6534
- onImageClick: handleImageClick,
6535
- onExecutionTraceClick,
6536
- messageActions,
6537
- retryDisabled: isWaitingForResponse,
6538
- typingSpeed: config.typingSpeed ?? 4,
6539
- userActionPrompts,
6540
- notifications,
6541
- onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
6542
- onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
6543
- onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
6544
- onExpireUserAction: isUserActionSupported ? expireUserAction2 : void 0,
6545
- onDismissNotification: dismissNotification,
6546
- onSubmitFeedback: handleSubmitFeedback
6547
- }
6548
- ),
6549
- /* @__PURE__ */ jsxRuntime.jsx(
6550
- StreamingIndicatorV2,
7817
+ initial: hasEverSentMessage ? { opacity: 0 } : false,
7818
+ animate: { opacity: 1 },
7819
+ transition: { duration: 0.3 },
7820
+ className: "payman-v2-chat-layout",
7821
+ children: [
7822
+ /* @__PURE__ */ jsxRuntime.jsx(
7823
+ MessageListV2,
7824
+ {
7825
+ ref: messageListV2Ref,
7826
+ messages,
7827
+ isStreaming: isWaitingForResponse,
7828
+ sidePanelOpen: !!pdfSheet,
7829
+ onEditUserMessage: handleEditMessageDraft,
7830
+ onRetryUserMessage: handleRetryUserMessage,
7831
+ onImageClick: handleImageClick,
7832
+ onExecutionTraceClick,
7833
+ messageActions,
7834
+ retryDisabled: isWaitingForResponse,
7835
+ typingSpeed: config.typingSpeed ?? 4,
7836
+ userActionPrompts,
7837
+ notifications,
7838
+ onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
7839
+ onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
7840
+ onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
7841
+ onDismissNotification: dismissNotification,
7842
+ onSubmitFeedback: handleSubmitFeedback
7843
+ }
7844
+ ),
7845
+ /* @__PURE__ */ jsxRuntime.jsx(
7846
+ StreamingIndicatorV2,
7847
+ {
7848
+ isStreaming: isWaitingForResponse,
7849
+ loadingAnimation: config.loadingAnimation
7850
+ }
7851
+ ),
7852
+ hasAskPermission && /* @__PURE__ */ jsxRuntime.jsx(
7853
+ ChatInputV2,
7854
+ {
7855
+ ref: chatInputV2Ref,
7856
+ onSend: handleV2Send,
7857
+ onCancel: cancelStream,
7858
+ disabled: isV2InputDisabled,
7859
+ isStreaming: isWaitingForResponse,
7860
+ isUploadingAttachments: attachmentUpload.isUploading,
7861
+ attachmentsReady: attachmentUpload.allReady,
7862
+ hasAttachmentUploadErrors: attachmentUpload.hasErrors,
7863
+ attachmentUploadStatusById: attachmentUpload.statusById,
7864
+ uploadedAttachmentPayloads: attachmentUpload.payloads,
7865
+ onAttachmentsChange: handleAttachmentsChange,
7866
+ placeholder: isRecording ? "Listening..." : placeholder,
7867
+ enableVoice: config.enableVoice === true,
7868
+ transcribedText: config.enableVoice === true ? transcribedText : "",
7869
+ voiceAvailable: config.enableVoice === true && voiceAvailable,
7870
+ isRecording,
7871
+ onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
7872
+ onCancelRecording: handleCancelRecording,
7873
+ onConfirmRecording: handleConfirmRecording,
7874
+ showResetSession,
7875
+ onResetSession: requestResetSession,
7876
+ showAttachmentButton: attachmentSettings.showAttachmentButton,
7877
+ showUploadImageButton: attachmentSettings.showUploadImageButton,
7878
+ showAttachFileButton: attachmentSettings.showAttachFileButton,
7879
+ allowedImageExtensions: attachmentSettings.allowedImageExtensions,
7880
+ allowedFileExtensions: attachmentSettings.allowedFileExtensions,
7881
+ maxCount: attachmentSettings.maxCount,
7882
+ maxFileBytes: attachmentSettings.maxFileBytes,
7883
+ maxTotalBytes: attachmentSettings.maxTotalBytes,
7884
+ onUploadImageClick,
7885
+ onAttachFileClick,
7886
+ editingMessageId,
7887
+ onClearEditing: handleClearEditing,
7888
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
7889
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
7890
+ slashCommands
7891
+ }
7892
+ )
7893
+ ]
7894
+ },
7895
+ "v2-chat"
7896
+ ) }) }),
7897
+ pdfPreviewMode === "split" && /* @__PURE__ */ jsxRuntime.jsx(
7898
+ PdfSheetV2,
6551
7899
  {
6552
- isStreaming: isWaitingForResponse,
6553
- loadingAnimation: config.loadingAnimation
7900
+ src: pdfSheet?.href ?? null,
7901
+ title: pdfSheet?.title ?? "",
7902
+ onClose: closePdfSheet,
7903
+ mode: "split"
6554
7904
  }
6555
7905
  ),
6556
- hasAskPermission && /* @__PURE__ */ jsxRuntime.jsx(
6557
- ChatInputV2,
7906
+ pdfPreviewMode === "sheet" && /* @__PURE__ */ jsxRuntime.jsx(
7907
+ "div",
6558
7908
  {
6559
- ref: chatInputV2Ref,
6560
- onSend: handleV2Send,
6561
- onCancel: cancelStream,
6562
- disabled: isV2InputDisabled,
6563
- isStreaming: isWaitingForResponse,
6564
- placeholder: isRecording ? "Listening..." : placeholder,
6565
- enableVoice: config.enableVoice === true,
6566
- transcribedText: config.enableVoice === true ? transcribedText : "",
6567
- voiceAvailable: config.enableVoice === true && voiceAvailable,
6568
- isRecording,
6569
- onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
6570
- onCancelRecording: handleCancelRecording,
6571
- onConfirmRecording: handleConfirmRecording,
6572
- showResetSession,
6573
- onResetSession: requestResetSession,
6574
- showAttachmentButton,
6575
- showUploadImageButton,
6576
- showAttachFileButton,
6577
- onUploadImageClick,
6578
- onAttachFileClick,
6579
- editingMessageId,
6580
- onClearEditing: handleClearEditing,
6581
- analysisMode: enableDeepModeToggle ? analysisMode : void 0,
6582
- onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
6583
- slashCommands
7909
+ className: "payman-v2-pdf-sheet-top-anchor",
7910
+ "aria-hidden": !pdfSheet,
7911
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7912
+ PdfSheetV2,
7913
+ {
7914
+ src: pdfSheet?.href ?? null,
7915
+ title: pdfSheet?.title ?? "",
7916
+ onClose: closePdfSheet,
7917
+ mode: "sheet"
7918
+ }
7919
+ )
6584
7920
  }
6585
7921
  )
6586
7922
  ]
6587
- },
6588
- "v2-chat"
6589
- ) }),
7923
+ }
7924
+ ),
6590
7925
  /* @__PURE__ */ jsxRuntime.jsx(
6591
7926
  ImageLightboxV2,
6592
7927
  {
@@ -6621,20 +7956,39 @@ var PaymanChat = react.forwardRef(
6621
7956
  function PaymanChat2(props, ref) {
6622
7957
  const mergedCallbacks = useSentryChatCallbacks(props.callbacks, props.config);
6623
7958
  const chat = useChatV2(props.config, mergedCallbacks);
6624
- return /* @__PURE__ */ jsxRuntime.jsx(PaymanChatInner, { ...props, chat, ref });
7959
+ const attachmentUpload = useAttachmentUpload(props.config);
7960
+ return /* @__PURE__ */ jsxRuntime.jsx(
7961
+ PaymanChatInner,
7962
+ {
7963
+ ...props,
7964
+ chat,
7965
+ attachmentUpload,
7966
+ ref
7967
+ }
7968
+ );
6625
7969
  }
6626
7970
  );
6627
7971
 
6628
7972
  exports.PaymanChat = PaymanChat;
6629
7973
  exports.PaymanChatContext = PaymanChatContext;
7974
+ exports.PdfSheetV2 = PdfSheetV2;
6630
7975
  exports.UserActionStaleError = UserActionStaleError;
7976
+ exports.buildSignedUrlEndpoint = buildSignedUrlEndpoint;
6631
7977
  exports.cancelUserAction = cancelUserAction;
6632
7978
  exports.captureSentryError = captureSentryError;
6633
7979
  exports.cn = cn;
6634
- exports.expireUserAction = expireUserAction;
7980
+ exports.formatAttachmentBytes = formatAttachmentBytes;
6635
7981
  exports.formatDate = formatDate;
7982
+ exports.getPdfTitleFromUrl = getPdfTitleFromUrl;
7983
+ exports.isPdfUrl = isPdfUrl;
7984
+ exports.mapExecutionHistoryPageToChatMessages = mapExecutionHistoryPageToChatMessages;
7985
+ exports.mapExecutionHistoryToChatMessages = mapExecutionHistoryToChatMessages;
6636
7986
  exports.resendUserAction = resendUserAction;
7987
+ exports.stripAttachmentsSuffixFromIntent = stripAttachmentsSuffixFromIntent;
6637
7988
  exports.submitUserAction = submitUserAction;
7989
+ exports.uploadAttachment = uploadAttachment;
7990
+ exports.uploadAttachments = uploadAttachments;
7991
+ exports.useAttachmentUpload = useAttachmentUpload;
6638
7992
  exports.useChatV2 = useChatV2;
6639
7993
  exports.usePaymanChat = usePaymanChat;
6640
7994
  exports.useVoice = useVoice;