@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.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { createContext, forwardRef, useCallback, useRef, useState, useMemo, useEffect, useImperativeHandle, useLayoutEffect, useContext } from 'react';
1
+ import { createContext, forwardRef, useCallback, useRef, useState, useImperativeHandle, useEffect, useContext, useMemo, useLayoutEffect, Children, isValidElement } from 'react';
2
2
  import { AnimatePresence, motion } from 'framer-motion';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import * as Sentry from '@sentry/react';
6
- import { ArrowDown, Pencil, X, RotateCcw, Telescope, Zap, Plus, ImagePlus, Paperclip, Mic, ArrowUp, Check, AlertCircle, Copy, WifiOff, ThumbsUp, ThumbsDown, Binoculars, Info, Download, Loader2, ChevronDown, User, Clock, Sparkles, ImageOff, Eye, ChevronRight, ShieldCheck } from 'lucide-react';
6
+ import { ArrowDown, Loader2, FileText, X, Pencil, RotateCcw, Telescope, Zap, Plus, ImagePlus, Paperclip, Mic, ArrowUp, Check, AlertCircle, Copy, WifiOff, ThumbsUp, ThumbsDown, Binoculars, Info, Download, ChevronDown, RefreshCw, ExternalLink, User, Clock, Sparkles, ImageOff, Eye, ChevronRight, ShieldCheck } from 'lucide-react';
7
7
  import ReactMarkdown from 'react-markdown';
8
8
  import remarkGfm from 'remark-gfm';
9
9
  import { createPortal } from 'react-dom';
@@ -13,7 +13,10 @@ import { DotLottieReact } from '@lottiefiles/dotlottie-react';
13
13
 
14
14
  var __defProp = Object.defineProperty;
15
15
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
16
- var __publicField = (obj, key, value) => __defNormalProp(obj, key + "", value);
16
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
17
+ var __defProp2 = Object.defineProperty;
18
+ var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
19
+ var __publicField2 = (obj, key, value) => __defNormalProp2(obj, key + "", value);
17
20
  function generateId() {
18
21
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
19
22
  const r = Math.random() * 16 | 0;
@@ -709,7 +712,8 @@ function buildRequestBody(config, userMessage, sessionId, options) {
709
712
  sessionAttributes,
710
713
  analysisMode: options?.analysisMode,
711
714
  locale: resolveLocale(config.locale),
712
- timezone: resolveTimezone(config.timezone)
715
+ timezone: resolveTimezone(config.timezone),
716
+ ...options?.attachments?.length ? { attachments: options.attachments } : {}
713
717
  };
714
718
  }
715
719
  function resolveLocale(configured) {
@@ -754,6 +758,7 @@ function buildResolveImagesUrl(config) {
754
758
  const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
755
759
  return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
756
760
  }
761
+ var NGROK_SKIP_BROWSER_WARNING = "ngrok-skip-browser-warning";
757
762
  function buildRequestHeaders(config) {
758
763
  const headers = {
759
764
  ...config.api.headers
@@ -761,6 +766,9 @@ function buildRequestHeaders(config) {
761
766
  if (config.api.authToken) {
762
767
  headers.Authorization = `Bearer ${config.api.authToken}`;
763
768
  }
769
+ if (!headers[NGROK_SKIP_BROWSER_WARNING]) {
770
+ headers[NGROK_SKIP_BROWSER_WARNING] = "true";
771
+ }
764
772
  return headers;
765
773
  }
766
774
  var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
@@ -1041,10 +1049,85 @@ function createCancelledMessageUpdate(steps, currentMessage) {
1041
1049
  currentMessage: currentMessage || "Thinking..."
1042
1050
  };
1043
1051
  }
1052
+ var DEFAULT_SIGNED_URL_ENDPOINT = "/api/files/signed-url";
1053
+ function fileExtension(filename) {
1054
+ const ext = filename.split(".").pop()?.toLowerCase().replace(/^\./, "");
1055
+ return ext && ext.length > 0 ? ext : "bin";
1056
+ }
1057
+ function resolveMimeType(file) {
1058
+ if (file.type && file.type.trim().length > 0) return file.type;
1059
+ const ext = fileExtension(file.name);
1060
+ const byExt = {
1061
+ pdf: "application/pdf",
1062
+ png: "image/png",
1063
+ jpg: "image/jpeg",
1064
+ jpeg: "image/jpeg",
1065
+ gif: "image/gif",
1066
+ webp: "image/webp"
1067
+ };
1068
+ return byExt[ext] ?? "application/octet-stream";
1069
+ }
1070
+ function buildSignedUrlEndpoint(config, ext) {
1071
+ const endpoint = config.api.signedUrlEndpoint || DEFAULT_SIGNED_URL_ENDPOINT;
1072
+ const queryParams = new URLSearchParams({ extn: ext.replace(/^\./, "") });
1073
+ return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
1074
+ }
1075
+ async function requestSignedUrl(config, ext, signal) {
1076
+ const url = buildSignedUrlEndpoint(config, ext);
1077
+ const headers = buildRequestHeaders(config);
1078
+ const response = await fetch(url, {
1079
+ method: "GET",
1080
+ headers,
1081
+ signal
1082
+ });
1083
+ if (!response.ok) {
1084
+ const errorText = await response.text().catch(() => "");
1085
+ throw new Error(
1086
+ errorText ? `Failed to get upload URL (${response.status}): ${errorText}` : `Failed to get upload URL (${response.status})`
1087
+ );
1088
+ }
1089
+ const data = await response.json();
1090
+ if (!data.key || !data.url) {
1091
+ throw new Error("Signed URL response missing key or url");
1092
+ }
1093
+ return { key: data.key, url: data.url };
1094
+ }
1095
+ async function uploadToSignedUrl(signedUrl, file, mimeType, signal) {
1096
+ const response = await fetch(signedUrl, {
1097
+ method: "PUT",
1098
+ headers: {
1099
+ "Content-Type": mimeType,
1100
+ "x-ms-blob-type": "BlockBlob"
1101
+ },
1102
+ body: file,
1103
+ signal
1104
+ });
1105
+ if (!response.ok) {
1106
+ const errorText = await response.text().catch(() => "");
1107
+ throw new Error(
1108
+ errorText ? `Failed to upload file (${response.status}): ${errorText}` : `Failed to upload file (${response.status})`
1109
+ );
1110
+ }
1111
+ }
1112
+ async function uploadAttachment(config, file, signal) {
1113
+ const ext = fileExtension(file.name);
1114
+ const mimeType = resolveMimeType(file);
1115
+ const { key, url } = await requestSignedUrl(config, ext, signal);
1116
+ await uploadToSignedUrl(url, file, mimeType, signal);
1117
+ return {
1118
+ tempKey: key,
1119
+ filename: file.name,
1120
+ mimeType
1121
+ };
1122
+ }
1123
+ async function uploadAttachments(config, files, signal) {
1124
+ const uploads = files.map((file) => uploadAttachment(config, file, signal));
1125
+ return Promise.all(uploads);
1126
+ }
1044
1127
  var UserActionStaleError = class extends Error {
1045
1128
  constructor(userActionId, message = "User action is no longer actionable") {
1046
1129
  super(message);
1047
- __publicField(this, "userActionId");
1130
+ __publicField2(this, "userActionId");
1048
1131
  this.name = "UserActionStaleError";
1049
1132
  this.userActionId = userActionId;
1050
1133
  }
@@ -1077,9 +1160,6 @@ async function cancelUserAction(config, userActionId) {
1077
1160
  async function resendUserAction(config, userActionId) {
1078
1161
  return sendUserActionRequest(config, userActionId, "resend");
1079
1162
  }
1080
- async function expireUserAction(config, userActionId) {
1081
- return sendUserActionRequest(config, userActionId, "expired");
1082
- }
1083
1163
  var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
1084
1164
  function upsertPrompt(prompts, req) {
1085
1165
  const active = { ...req, status: "pending" };
@@ -1108,12 +1188,32 @@ function getStoredOrInitialMessages(config) {
1108
1188
  function getSessionIdFromMessages(messages) {
1109
1189
  return messages.find((message) => message.sessionId)?.sessionId;
1110
1190
  }
1191
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "gif", "webp", "avif"]);
1192
+ function attachmentKindFromFile(file) {
1193
+ const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
1194
+ if (IMAGE_EXTENSIONS.has(ext) || file.type.startsWith("image/")) return "image";
1195
+ return "file";
1196
+ }
1197
+ function buildMessageAttachments(files) {
1198
+ return files.map((file, index) => {
1199
+ const kind = attachmentKindFromFile(file);
1200
+ return {
1201
+ id: `att-${Date.now()}-${index}`,
1202
+ filename: file.name,
1203
+ mimeType: file.type || "application/octet-stream",
1204
+ // Blob URL for all local files so PDFs/docs stay previewable after send.
1205
+ previewUrl: URL.createObjectURL(file),
1206
+ kind
1207
+ };
1208
+ });
1209
+ }
1111
1210
  function useChatV2(config, callbacks = {}) {
1112
1211
  const [messages, setMessages] = useState(() => getStoredOrInitialMessages(config));
1113
1212
  const [isWaitingForResponse, setIsWaitingForResponse] = useState(() => {
1114
1213
  if (!config.userId) return false;
1115
1214
  return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1116
1215
  });
1216
+ const [isUploadingAttachments, setIsUploadingAttachments] = useState(false);
1117
1217
  const sessionIdRef = useRef(
1118
1218
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1119
1219
  );
@@ -1188,21 +1288,45 @@ function useChatV2(config, callbacks = {}) {
1188
1288
  );
1189
1289
  const sendMessage = useCallback(
1190
1290
  async (userMessage, options) => {
1191
- if (!userMessage.trim()) return;
1291
+ const trimmedMessage = userMessage.trim();
1292
+ const files = options?.files ?? [];
1293
+ const hasPreuploadedAttachments = (options?.attachments?.length ?? 0) > 0;
1294
+ if (!trimmedMessage && files.length === 0 && !hasPreuploadedAttachments) return;
1295
+ let streamAttachments = options?.attachments;
1296
+ if (!streamAttachments && files.length > 0) {
1297
+ setIsUploadingAttachments(true);
1298
+ try {
1299
+ streamAttachments = await uploadAttachments(configRef.current, files);
1300
+ } catch (error) {
1301
+ if (error.name !== "AbortError") {
1302
+ callbacksRef.current.onError?.(error);
1303
+ }
1304
+ throw error;
1305
+ } finally {
1306
+ setIsUploadingAttachments(false);
1307
+ }
1308
+ }
1192
1309
  if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1193
1310
  sessionIdRef.current = generateId();
1194
1311
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1195
1312
  }
1313
+ const messageAttachments = files.length > 0 ? buildMessageAttachments(files) : streamAttachments?.length ? streamAttachments.map((attachment, index) => ({
1314
+ id: `att-${Date.now()}-${index}`,
1315
+ filename: attachment.filename,
1316
+ mimeType: attachment.mimeType,
1317
+ kind: attachment.mimeType.startsWith("image/") ? "image" : "file"
1318
+ })) : void 0;
1196
1319
  const userMessageId = `user-${Date.now()}`;
1197
1320
  const userMsg = {
1198
1321
  id: userMessageId,
1199
1322
  sessionId: sessionIdRef.current,
1200
1323
  role: "user",
1201
- content: userMessage,
1202
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1324
+ content: trimmedMessage,
1325
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1326
+ attachments: messageAttachments
1203
1327
  };
1204
1328
  setMessages((prev) => [...prev, userMsg]);
1205
- callbacksRef.current.onMessageSent?.(userMessage);
1329
+ callbacksRef.current.onMessageSent?.(trimmedMessage);
1206
1330
  setIsWaitingForResponse(true);
1207
1331
  callbacksRef.current.onStreamStart?.();
1208
1332
  const streamingId = `assistant-${Date.now()}`;
@@ -1229,11 +1353,14 @@ function useChatV2(config, callbacks = {}) {
1229
1353
  activeStreamStore.start(userId, abortController, initialMessages);
1230
1354
  }
1231
1355
  const newSessionId = await startStream(
1232
- userMessage,
1356
+ trimmedMessage,
1233
1357
  streamingId,
1234
1358
  sessionIdRef.current,
1235
1359
  abortController,
1236
- options
1360
+ {
1361
+ analysisMode: options?.analysisMode,
1362
+ attachments: streamAttachments
1363
+ }
1237
1364
  );
1238
1365
  const finalStreamUserId = streamUserIdRef.current ?? userId;
1239
1366
  if (finalStreamUserId) {
@@ -1371,19 +1498,6 @@ function useChatV2(config, callbacks = {}) {
1371
1498
  },
1372
1499
  [setPromptStatus]
1373
1500
  );
1374
- const expireUserAction2 = useCallback(
1375
- async (userActionId) => {
1376
- setPromptStatus(userActionId, "expired");
1377
- try {
1378
- await expireUserAction(configRef.current, userActionId);
1379
- } catch (error) {
1380
- if (error instanceof UserActionStaleError) return;
1381
- callbacksRef.current.onError?.(error);
1382
- throw error;
1383
- }
1384
- },
1385
- [setPromptStatus]
1386
- );
1387
1501
  const dismissNotification = useCallback((id) => {
1388
1502
  setUserActionState((prev) => ({
1389
1503
  ...prev,
@@ -1424,6 +1538,18 @@ function useChatV2(config, callbacks = {}) {
1424
1538
  setMessages(config.initialMessages);
1425
1539
  sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
1426
1540
  }, [config.initialMessages, config.initialSessionId, config.userId]);
1541
+ const hydratedSessionIdRef = useRef(void 0);
1542
+ useEffect(() => {
1543
+ if (config.userId) return;
1544
+ if (!config.initialMessages?.length) return;
1545
+ const sessionId = config.initialSessionId;
1546
+ if (hydratedSessionIdRef.current === sessionId && messagesRef.current.length > 0) {
1547
+ return;
1548
+ }
1549
+ hydratedSessionIdRef.current = sessionId;
1550
+ setMessages(config.initialMessages);
1551
+ sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? sessionId;
1552
+ }, [config.initialMessages, config.initialSessionId, config.userId]);
1427
1553
  useEffect(() => {
1428
1554
  const prevUserId = prevUserIdRef.current;
1429
1555
  prevUserIdRef.current = config.userId;
@@ -1458,12 +1584,12 @@ function useChatV2(config, callbacks = {}) {
1458
1584
  getSessionId,
1459
1585
  getMessages,
1460
1586
  isWaitingForResponse,
1587
+ isUploadingAttachments,
1461
1588
  sessionId: sessionIdRef.current,
1462
1589
  userActionState,
1463
1590
  submitUserAction: submitUserAction2,
1464
1591
  cancelUserAction: cancelUserAction2,
1465
1592
  resendUserAction: resendUserAction2,
1466
- expireUserAction: expireUserAction2,
1467
1593
  dismissNotification
1468
1594
  };
1469
1595
  }
@@ -1682,6 +1808,102 @@ function useVoice(config = {}, callbacks = {}) {
1682
1808
  reset
1683
1809
  };
1684
1810
  }
1811
+ function useAttachmentUpload(config) {
1812
+ const configRef = useRef(config);
1813
+ configRef.current = config;
1814
+ const [entries, setEntries] = useState(/* @__PURE__ */ new Map());
1815
+ const [orderedIds, setOrderedIds] = useState([]);
1816
+ const abortControllersRef = useRef(/* @__PURE__ */ new Map());
1817
+ const activeIdsRef = useRef(/* @__PURE__ */ new Set());
1818
+ const startUpload = useCallback((id, file) => {
1819
+ if (activeIdsRef.current.has(id)) return;
1820
+ activeIdsRef.current.add(id);
1821
+ const controller = new AbortController();
1822
+ abortControllersRef.current.set(id, controller);
1823
+ setEntries((prev) => {
1824
+ const next = new Map(prev);
1825
+ next.set(id, { id, status: "uploading" });
1826
+ return next;
1827
+ });
1828
+ void uploadAttachment(configRef.current, file, controller.signal).then((payload) => {
1829
+ if (controller.signal.aborted) return;
1830
+ setEntries((prev) => {
1831
+ const next = new Map(prev);
1832
+ next.set(id, { id, status: "done", payload });
1833
+ return next;
1834
+ });
1835
+ }).catch((error) => {
1836
+ if (error.name === "AbortError") return;
1837
+ setEntries((prev) => {
1838
+ const next = new Map(prev);
1839
+ next.set(id, {
1840
+ id,
1841
+ status: "error",
1842
+ error: error.message || "Upload failed"
1843
+ });
1844
+ return next;
1845
+ });
1846
+ }).finally(() => {
1847
+ abortControllersRef.current.delete(id);
1848
+ });
1849
+ }, []);
1850
+ const syncAttachments = useCallback(
1851
+ (attachments) => {
1852
+ const nextIds = attachments.map((attachment) => attachment.id);
1853
+ setOrderedIds(nextIds);
1854
+ const nextIdSet = new Set(nextIds);
1855
+ for (const id of activeIdsRef.current) {
1856
+ if (nextIdSet.has(id)) continue;
1857
+ abortControllersRef.current.get(id)?.abort();
1858
+ abortControllersRef.current.delete(id);
1859
+ activeIdsRef.current.delete(id);
1860
+ }
1861
+ setEntries((prev) => {
1862
+ const next = new Map(prev);
1863
+ for (const id of next.keys()) {
1864
+ if (!nextIdSet.has(id)) next.delete(id);
1865
+ }
1866
+ return next;
1867
+ });
1868
+ for (const attachment of attachments) {
1869
+ startUpload(attachment.id, attachment.file);
1870
+ }
1871
+ },
1872
+ [startUpload]
1873
+ );
1874
+ const clearAll = useCallback(() => {
1875
+ for (const controller of abortControllersRef.current.values()) {
1876
+ controller.abort();
1877
+ }
1878
+ abortControllersRef.current.clear();
1879
+ activeIdsRef.current.clear();
1880
+ setOrderedIds([]);
1881
+ setEntries(/* @__PURE__ */ new Map());
1882
+ }, []);
1883
+ const entryList = useMemo(
1884
+ () => orderedIds.map((id) => entries.get(id)).filter((entry) => entry != null),
1885
+ [entries, orderedIds]
1886
+ );
1887
+ const isUploading = entryList.some((entry) => entry.status === "uploading");
1888
+ const hasErrors = entryList.some((entry) => entry.status === "error");
1889
+ const allReady = orderedIds.length === 0 || orderedIds.every((id) => entries.get(id)?.status === "done");
1890
+ const payloads = entryList.filter((entry) => entry.status === "done" && entry.payload).map((entry) => entry.payload);
1891
+ const statusById = useMemo(
1892
+ () => Object.fromEntries(
1893
+ entryList.map((entry) => [entry.id, entry.status])
1894
+ ),
1895
+ [entryList]
1896
+ );
1897
+ return {
1898
+ syncAttachments,
1899
+ clearAll,
1900
+ isUploading,
1901
+ hasErrors,
1902
+ allReady,
1903
+ payloads,
1904
+ statusById
1905
+ };
1906
+ }
1685
1907
  function classifyField(field) {
1686
1908
  if (!field) return "text";
1687
1909
  if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
@@ -1798,6 +2020,66 @@ function buildContent(schema, values) {
1798
2020
  }
1799
2021
  return content;
1800
2022
  }
2023
+ var ATTACHMENTS_SUFFIX_RE = /\n\n\[Attachments:[^\]]*\]\s*$/;
2024
+ function stripAttachmentsSuffixFromIntent(intent) {
2025
+ return intent.replace(ATTACHMENTS_SUFFIX_RE, "").trimEnd();
2026
+ }
2027
+ function mapFeedback(feedback) {
2028
+ if (!feedback?.feedback) return null;
2029
+ return feedback.feedback === "POSITIVE" ? "up" : "down";
2030
+ }
2031
+ function mapHistoryAttachments(executionId, attachments) {
2032
+ const mapped = (attachments ?? []).map((attachment, index) => ({
2033
+ id: `${executionId}:att:${index}`,
2034
+ filename: attachment.filename,
2035
+ mimeType: attachment.mimeType,
2036
+ url: attachment.url ?? void 0,
2037
+ kind: attachment.mimeType.startsWith("image/") ? "image" : "file"
2038
+ }));
2039
+ return mapped.length > 0 ? mapped : void 0;
2040
+ }
2041
+ function mapExecutionHistoryToChatMessages(message) {
2042
+ const timestamp = message.startTime || message.endTime || (/* @__PURE__ */ new Date()).toISOString();
2043
+ const executionId = message.executionId ?? message.traceId ?? message.id;
2044
+ const attachments = mapHistoryAttachments(message.id, message.attachments);
2045
+ const rows = [
2046
+ {
2047
+ id: `${message.id}:user`,
2048
+ role: "user",
2049
+ content: stripAttachmentsSuffixFromIntent(message.sessionUserIntent),
2050
+ timestamp,
2051
+ attachments
2052
+ }
2053
+ ];
2054
+ if (message.agentResponse) {
2055
+ rows.push({
2056
+ id: `${message.id}:assistant`,
2057
+ role: "assistant",
2058
+ content: message.agentResponse,
2059
+ timestamp: message.endTime || timestamp,
2060
+ isError: message.status === "FAILED",
2061
+ executionId,
2062
+ feedback: mapFeedback(message.feedback)
2063
+ });
2064
+ }
2065
+ return rows;
2066
+ }
2067
+ function mapExecutionHistoryPageToChatMessages(messages) {
2068
+ return messages.flatMap(mapExecutionHistoryToChatMessages);
2069
+ }
2070
+ function attachmentDisplayUrl(attachment) {
2071
+ return attachment.previewUrl ?? attachment.url;
2072
+ }
2073
+ function isImageAttachment(attachment) {
2074
+ const displayUrl = attachmentDisplayUrl(attachment);
2075
+ return attachment.kind === "image" || Boolean(displayUrl) && attachment.mimeType.startsWith("image/");
2076
+ }
2077
+ function isPdfAttachmentMeta(attachment) {
2078
+ return attachment.mimeType === "application/pdf" || /\.pdf$/i.test(attachment.filename);
2079
+ }
2080
+ function isPdfFile(file) {
2081
+ return file.type === "application/pdf" || /\.pdf$/i.test(file.name);
2082
+ }
1801
2083
  var PaymanChatContext = createContext(void 0);
1802
2084
  function usePaymanChat() {
1803
2085
  const context = useContext(PaymanChatContext);
@@ -1908,6 +2190,111 @@ function subscribeToCfRay(urlPattern, listener) {
1908
2190
  };
1909
2191
  }
1910
2192
 
2193
+ // src/utils/attachmentConfig.ts
2194
+ var DEFAULT_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp"];
2195
+ var DEFAULT_DOCUMENT_EXTENSIONS = ["pdf", "docx", "xlsx", "xls"];
2196
+ function resolveChatAttachmentConfig(config) {
2197
+ const nested = config.attachments;
2198
+ const enabled = nested?.enabled ?? config.showAttachmentButton ?? true;
2199
+ const uploadImage = nested?.uploadImage ?? config.showUploadImageButton ?? true;
2200
+ const attachFile = nested?.attachFile ?? config.showAttachFileButton ?? true;
2201
+ return {
2202
+ showAttachmentButton: enabled && (uploadImage || attachFile),
2203
+ showUploadImageButton: enabled && uploadImage,
2204
+ showAttachFileButton: enabled && attachFile,
2205
+ maxCount: nested?.maxCount ?? nested?.maxImages ?? nested?.maxDocuments,
2206
+ maxFileBytes: nested?.maxFileBytes,
2207
+ maxTotalBytes: nested?.maxTotalBytes,
2208
+ allowedImageExtensions: nested?.imageExtensions ?? DEFAULT_IMAGE_EXTENSIONS,
2209
+ allowedFileExtensions: nested?.documentExtensions ?? DEFAULT_DOCUMENT_EXTENSIONS
2210
+ };
2211
+ }
2212
+
2213
+ // src/utils/formatAttachmentBytes.ts
2214
+ function formatAttachmentBytes(bytes) {
2215
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
2216
+ const units = ["B", "KB", "MB", "GB"];
2217
+ let value = bytes;
2218
+ let unitIndex = 0;
2219
+ while (value >= 1024 && unitIndex < units.length - 1) {
2220
+ value /= 1024;
2221
+ unitIndex += 1;
2222
+ }
2223
+ const rounded = unitIndex === 0 ? String(Math.round(value)) : value >= 10 ? value.toFixed(0) : value.toFixed(1).replace(/\.0$/, "");
2224
+ return `${rounded} ${units[unitIndex]}`;
2225
+ }
2226
+
2227
+ // src/utils/pdfLink.ts
2228
+ function filenameFromContentDisposition(value) {
2229
+ if (!value) return void 0;
2230
+ let decoded = value;
2231
+ try {
2232
+ decoded = decodeURIComponent(value);
2233
+ } catch {
2234
+ decoded = value;
2235
+ }
2236
+ const quoted = decoded.match(/filename\*=(?:UTF-8''([^;]+)|"([^"]+)")/i);
2237
+ if (quoted?.[1] || quoted?.[2]) {
2238
+ return (quoted[1] ?? quoted[2])?.trim();
2239
+ }
2240
+ const unquoted = decoded.match(/filename=([^;]+)/i);
2241
+ return unquoted?.[1]?.replace(/"/g, "").trim();
2242
+ }
2243
+ function filenameFromUrlPath(href) {
2244
+ try {
2245
+ const parts = new URL(href).pathname.split("/").filter(Boolean);
2246
+ const last = parts[parts.length - 1];
2247
+ return last ? decodeURIComponent(last) : void 0;
2248
+ } catch {
2249
+ return void 0;
2250
+ }
2251
+ }
2252
+ function isPdfUrl(href) {
2253
+ if (!href) return false;
2254
+ const lower = href.toLowerCase();
2255
+ if (lower.includes(".pdf")) return true;
2256
+ try {
2257
+ const url = new URL(href);
2258
+ if (url.pathname.toLowerCase().endsWith(".pdf")) return true;
2259
+ const filename = url.searchParams.get("filename");
2260
+ if (filename?.toLowerCase().includes(".pdf")) return true;
2261
+ for (const key of ["rscd", "response-content-disposition"]) {
2262
+ const fromDisposition = filenameFromContentDisposition(
2263
+ url.searchParams.get(key)
2264
+ );
2265
+ if (fromDisposition?.toLowerCase().includes(".pdf")) return true;
2266
+ }
2267
+ } catch {
2268
+ return lower.endsWith(".pdf");
2269
+ }
2270
+ return false;
2271
+ }
2272
+ function getPdfTitleFromUrl(href, linkText) {
2273
+ const text = linkText?.trim();
2274
+ if (text) return text;
2275
+ try {
2276
+ const url = new URL(href);
2277
+ const filename = url.searchParams.get("filename");
2278
+ if (filename) {
2279
+ return filename.replace(/\.pdf$/i, "").replace(/[-_]+/g, " ").trim();
2280
+ }
2281
+ for (const key of ["rscd", "response-content-disposition"]) {
2282
+ const fromDisposition = filenameFromContentDisposition(
2283
+ url.searchParams.get(key)
2284
+ );
2285
+ if (fromDisposition) {
2286
+ return fromDisposition.replace(/\.pdf$/i, "").replace(/[-_]+/g, " ").trim();
2287
+ }
2288
+ }
2289
+ } catch {
2290
+ }
2291
+ const fromPath = filenameFromUrlPath(href);
2292
+ if (fromPath) {
2293
+ return fromPath.replace(/\.pdf$/i, "").replace(/[-_]+/g, " ").trim();
2294
+ }
2295
+ return "Document";
2296
+ }
2297
+
1911
2298
  // src/utils/slashCommands.ts
1912
2299
  var DEFAULT_SLASH_COMMANDS = [
1913
2300
  {
@@ -3059,6 +3446,108 @@ function ActionTooltipV2({ label, children }) {
3059
3446
  ] }) })
3060
3447
  ] });
3061
3448
  }
3449
+ function FilePreviewShell({
3450
+ filename,
3451
+ typeLabel,
3452
+ onClick,
3453
+ thumbnail,
3454
+ className,
3455
+ "aria-label": ariaLabel
3456
+ }) {
3457
+ const shellClass = cn(
3458
+ "payman-v2-file-preview-shell",
3459
+ "payman-v2-file-preview-shell-sent",
3460
+ onClick && "payman-v2-file-preview-shell-clickable",
3461
+ className
3462
+ );
3463
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
3464
+ /* @__PURE__ */ jsx("div", { className: "payman-v2-file-preview-thumb", children: thumbnail ?? /* @__PURE__ */ jsx(
3465
+ FileText,
3466
+ {
3467
+ size: 16,
3468
+ strokeWidth: 1.75,
3469
+ className: "payman-v2-file-preview-doc-icon"
3470
+ }
3471
+ ) }),
3472
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-file-preview-info", children: [
3473
+ /* @__PURE__ */ jsx("span", { className: "payman-v2-file-preview-name", children: filename }),
3474
+ /* @__PURE__ */ jsx("span", { className: "payman-v2-file-preview-type", children: typeLabel })
3475
+ ] })
3476
+ ] });
3477
+ if (onClick) {
3478
+ return /* @__PURE__ */ jsx(
3479
+ "button",
3480
+ {
3481
+ type: "button",
3482
+ className: shellClass,
3483
+ onClick,
3484
+ "aria-label": ariaLabel ?? `Open ${filename}`,
3485
+ children: body
3486
+ }
3487
+ );
3488
+ }
3489
+ return /* @__PURE__ */ jsx("div", { className: shellClass, children: body });
3490
+ }
3491
+ function FilePreviewBlockLayout({
3492
+ children,
3493
+ className,
3494
+ ...rest
3495
+ }) {
3496
+ return /* @__PURE__ */ jsx("div", { className: cn("payman-v2-file-preview-item", className), ...rest, children });
3497
+ }
3498
+ function attachmentTypeLabel(attachment) {
3499
+ if (isImageAttachment(attachment)) {
3500
+ return "Image";
3501
+ }
3502
+ const ext = attachment.filename.split(".").pop()?.toUpperCase();
3503
+ return ext || "Document";
3504
+ }
3505
+ function isPdfAttachment(attachment) {
3506
+ const displayUrl = attachmentDisplayUrl(attachment);
3507
+ return isPdfAttachmentMeta(attachment) || Boolean(displayUrl) && isPdfUrl(displayUrl);
3508
+ }
3509
+ function AttachmentPreviewBlock({
3510
+ attachment,
3511
+ onImageClick
3512
+ }) {
3513
+ const chatContext = useContext(PaymanChatContext);
3514
+ const displayUrl = attachmentDisplayUrl(attachment);
3515
+ const typeLabel = attachmentTypeLabel(attachment);
3516
+ const isImage = isImageAttachment(attachment) && displayUrl;
3517
+ const openAttachment = () => {
3518
+ if (!displayUrl) return;
3519
+ if (isImageAttachment(attachment)) {
3520
+ onImageClick?.(displayUrl, attachment.filename);
3521
+ return;
3522
+ }
3523
+ if (isPdfAttachment(attachment)) {
3524
+ chatContext?.openPdfSheet(displayUrl, attachment.filename);
3525
+ return;
3526
+ }
3527
+ window.open(displayUrl, "_blank", "noopener,noreferrer");
3528
+ };
3529
+ if (isImage) {
3530
+ return /* @__PURE__ */ jsx(FilePreviewBlockLayout, { children: /* @__PURE__ */ jsx(
3531
+ FilePreviewShell,
3532
+ {
3533
+ filename: attachment.filename,
3534
+ typeLabel,
3535
+ onClick: openAttachment,
3536
+ "aria-label": `Preview ${attachment.filename}`,
3537
+ thumbnail: /* @__PURE__ */ jsx("img", { src: displayUrl, alt: "", draggable: false })
3538
+ }
3539
+ ) });
3540
+ }
3541
+ return /* @__PURE__ */ jsx(FilePreviewBlockLayout, { children: /* @__PURE__ */ jsx(
3542
+ FilePreviewShell,
3543
+ {
3544
+ filename: attachment.filename,
3545
+ typeLabel,
3546
+ onClick: displayUrl ? openAttachment : void 0,
3547
+ "aria-label": `Open ${attachment.filename}`
3548
+ }
3549
+ ) });
3550
+ }
3062
3551
  function formatMessageTime(timestamp) {
3063
3552
  const value = new Date(timestamp);
3064
3553
  if (Number.isNaN(value.getTime())) return "";
@@ -3071,6 +3560,7 @@ function UserMessageV2({
3071
3560
  message,
3072
3561
  onEdit,
3073
3562
  onRetry,
3563
+ onImageClick,
3074
3564
  retryDisabled = false,
3075
3565
  actions
3076
3566
  }) {
@@ -3153,7 +3643,15 @@ function UserMessageV2({
3153
3643
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3154
3644
  toastPortal,
3155
3645
  /* @__PURE__ */ jsx("div", { className: "payman-v2-user-msg payman-v2-fade-in", children: /* @__PURE__ */ jsxs("div", { className: "payman-v2-user-msg-group", children: [
3156
- /* @__PURE__ */ jsx("div", { className: "payman-v2-user-msg-bubble", children: parsedCommand ? /* @__PURE__ */ jsxs("div", { className: "payman-v2-user-msg-command", children: [
3646
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "payman-v2-file-preview payman-v2-user-msg-attachments", children: message.attachments.map((attachment) => /* @__PURE__ */ jsx(
3647
+ AttachmentPreviewBlock,
3648
+ {
3649
+ attachment,
3650
+ onImageClick
3651
+ },
3652
+ attachment.id
3653
+ )) }),
3654
+ (message.content.trim().length > 0 || !message.attachments?.length) && /* @__PURE__ */ jsx("div", { className: "payman-v2-user-msg-bubble", children: parsedCommand ? /* @__PURE__ */ jsxs("div", { className: "payman-v2-user-msg-command", children: [
3157
3655
  /* @__PURE__ */ jsx("span", { className: "payman-v2-user-msg-command-chip", children: parsedCommand.command }),
3158
3656
  parsedCommand.body.trim() ? /* @__PURE__ */ jsx("p", { className: "payman-v2-user-msg-text", children: parsedCommand.body }) : null
3159
3657
  ] }) : /* @__PURE__ */ jsx("p", { className: "payman-v2-user-msg-text", children: message.content }) }),
@@ -3378,9 +3876,44 @@ function MarkdownImageV2({
3378
3876
  }
3379
3877
  ) });
3380
3878
  }
3381
- function buildComponents(onImageClick, isResolvingRef) {
3879
+ function PdfBlockV2({ title, href, onOpen, autoOpen = false }) {
3880
+ const autoOpenedHrefRef = useRef(null);
3881
+ useEffect(() => {
3882
+ if (!autoOpen || autoOpenedHrefRef.current === href) return;
3883
+ autoOpenedHrefRef.current = href;
3884
+ onOpen(href, title, { auto: true });
3885
+ }, [href, autoOpen]);
3886
+ return /* @__PURE__ */ jsx(FilePreviewBlockLayout, { "data-payman-file-block": true, children: /* @__PURE__ */ jsx(
3887
+ FilePreviewShell,
3888
+ {
3889
+ filename: title,
3890
+ typeLabel: "PDF",
3891
+ onClick: () => onOpen(href, title),
3892
+ "aria-label": `Open PDF: ${title}`
3893
+ }
3894
+ ) });
3895
+ }
3896
+ function childrenToText(children) {
3897
+ if (typeof children === "string") return children;
3898
+ if (typeof children === "number") return String(children);
3899
+ if (Array.isArray(children)) return children.map(childrenToText).join("");
3900
+ if (children && typeof children === "object" && "props" in children) {
3901
+ return childrenToText(children.props.children);
3902
+ }
3903
+ return "";
3904
+ }
3905
+ function isFileBlockChild(child) {
3906
+ return isValidElement(child) && typeof child.props === "object" && child.props !== null && "data-payman-file-block" in child.props;
3907
+ }
3908
+ function buildComponents(onImageClick, isResolvingRef, onPdfClick, autoOpenPdf) {
3382
3909
  return {
3383
- p: ({ children }) => /* @__PURE__ */ jsx("p", { children }),
3910
+ p: ({ children }) => {
3911
+ const childArray = Children.toArray(children);
3912
+ if (childArray.length === 1 && isFileBlockChild(childArray[0])) {
3913
+ return childArray[0];
3914
+ }
3915
+ return /* @__PURE__ */ jsx("p", { children });
3916
+ },
3384
3917
  code: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
3385
3918
  pre: ({ children }) => /* @__PURE__ */ jsx("div", { className: "payman-v2-markdown-pre", children: /* @__PURE__ */ jsx("pre", { children }) }),
3386
3919
  ul: ({ children }) => /* @__PURE__ */ jsx("ul", { children }),
@@ -3393,7 +3926,15 @@ function buildComponents(onImageClick, isResolvingRef) {
3393
3926
  em: ({ children }) => /* @__PURE__ */ jsx("em", { children }),
3394
3927
  blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", { children }),
3395
3928
  hr: () => /* @__PURE__ */ jsx("hr", {}),
3396
- a: ({ href, children }) => /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children }),
3929
+ a: ({ href, children }) => {
3930
+ const url = href ?? "";
3931
+ if (onPdfClick && isPdfUrl(url)) {
3932
+ const linkText = childrenToText(children).trim();
3933
+ const title = getPdfTitleFromUrl(url, linkText);
3934
+ return /* @__PURE__ */ jsx(PdfBlockV2, { href: url, title, onOpen: onPdfClick, autoOpen: autoOpenPdf });
3935
+ }
3936
+ return /* @__PURE__ */ jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children });
3937
+ },
3397
3938
  img: ({ src, alt }) => /* @__PURE__ */ jsx(
3398
3939
  MarkdownImageV2,
3399
3940
  {
@@ -3414,13 +3955,15 @@ function MarkdownRendererV2({
3414
3955
  content,
3415
3956
  isStreaming,
3416
3957
  isResolvingImages,
3417
- onImageClick
3958
+ onImageClick,
3959
+ onPdfClick,
3960
+ autoOpenPdf
3418
3961
  }) {
3419
3962
  const isResolvingRef = useRef(isResolvingImages);
3420
3963
  isResolvingRef.current = isResolvingImages;
3421
3964
  const components = useMemo(
3422
- () => buildComponents(onImageClick, isResolvingRef),
3423
- [onImageClick]
3965
+ () => buildComponents(onImageClick, isResolvingRef, onPdfClick, autoOpenPdf),
3966
+ [onImageClick, onPdfClick, autoOpenPdf]
3424
3967
  );
3425
3968
  return /* @__PURE__ */ jsx(
3426
3969
  "div",
@@ -3789,6 +4332,17 @@ function stripIncompleteImageToken(text) {
3789
4332
  if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
3790
4333
  return text.slice(0, lastBang);
3791
4334
  }
4335
+ function stripIncompleteLinkToken(text) {
4336
+ const lastBracket = text.lastIndexOf("[");
4337
+ if (lastBracket === -1) return text;
4338
+ if (lastBracket > 0 && text[lastBracket - 1] === "!") return text;
4339
+ const after = text.slice(lastBracket);
4340
+ if (/^\[[^\]]*\]\([^)]*\)/.test(after)) return text;
4341
+ return text.slice(0, lastBracket);
4342
+ }
4343
+ function stripIncompleteMarkdownTokens(text) {
4344
+ return stripIncompleteLinkToken(stripIncompleteImageToken(text));
4345
+ }
3792
4346
  function getFeedbackState(message) {
3793
4347
  const feedback = message.feedback;
3794
4348
  if (feedback === "up" || feedback === "down") return feedback;
@@ -3812,6 +4366,12 @@ function AssistantMessageV2({
3812
4366
  () => getFeedbackState(message)
3813
4367
  );
3814
4368
  const [reasonModalOpen, setReasonModalOpen] = useState(false);
4369
+ const chatContext = useContext(PaymanChatContext);
4370
+ const chatContextRef = useRef(chatContext);
4371
+ chatContextRef.current = chatContext;
4372
+ const handlePdfClick = useCallback((href, title, options) => {
4373
+ chatContextRef.current?.openPdfSheet(href, title, options);
4374
+ }, []);
3815
4375
  const canSubmitFeedback = !!onSubmitFeedback && !!message.executionId;
3816
4376
  const [toast, setToast] = useState(null);
3817
4377
  const copyResetTimerRef = useRef(null);
@@ -3836,7 +4396,7 @@ function AssistantMessageV2({
3836
4396
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
3837
4397
  if (!raw) return "";
3838
4398
  const normalized = raw.replace(/\\n/g, "\n");
3839
- return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
4399
+ return message.isStreaming ? stripIncompleteMarkdownTokens(normalized) : normalized;
3840
4400
  })();
3841
4401
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
3842
4402
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
@@ -4006,10 +4566,12 @@ function AssistantMessageV2({
4006
4566
  /* @__PURE__ */ jsx("div", { className: "payman-v2-assistant-msg-content-area", children: displayContent ? /* @__PURE__ */ jsx(
4007
4567
  MarkdownRendererV2,
4008
4568
  {
4009
- content: displayContent,
4569
+ content: message.isStreaming && !isCancelled || isResponseTyping ? stripIncompleteMarkdownTokens(displayContent) : displayContent,
4010
4570
  isStreaming: message.isStreaming && !isCancelled || isResponseTyping,
4011
4571
  isResolvingImages: message.isResolvingImages,
4012
- onImageClick
4572
+ onImageClick,
4573
+ onPdfClick: handlePdfClick,
4574
+ autoOpenPdf: hasEverStreamed.current
4013
4575
  }
4014
4576
  ) : !isThinkingStreaming ? /* @__PURE__ */ jsx("span", { className: "payman-v2-assistant-msg-placeholder", children: "..." }) : null }),
4015
4577
  isCancelled && message.isStreaming && /* @__PURE__ */ jsxs("div", { className: "payman-v2-assistant-msg-paused", children: [
@@ -4226,7 +4788,6 @@ function VerificationInline({
4226
4788
  const [code, setCode] = useState("");
4227
4789
  const [errored, setErrored] = useState(false);
4228
4790
  const [resendSec, setResendSec] = useState(0);
4229
- const [hiddenAfterResend, setHiddenAfterResend] = useState(false);
4230
4791
  const lastSubmittedRef = useRef(null);
4231
4792
  const resendTimerRef = useRef(void 0);
4232
4793
  const status = prompt.status;
@@ -4240,9 +4801,6 @@ function VerificationInline({
4240
4801
  lastSubmittedRef.current = null;
4241
4802
  }
4242
4803
  }, [prompt.subAction, prompt.userActionId]);
4243
- useEffect(() => {
4244
- setHiddenAfterResend(false);
4245
- }, [prompt.expirySeconds, prompt.message, prompt.subAction, prompt.userActionId]);
4246
4804
  useEffect(() => {
4247
4805
  if (code.length < codeLen) lastSubmittedRef.current = null;
4248
4806
  }, [code, codeLen]);
@@ -4293,13 +4851,11 @@ function VerificationInline({
4293
4851
  if (locked || resendSec > 0) return;
4294
4852
  setErrored(false);
4295
4853
  setCode("");
4296
- setHiddenAfterResend(true);
4297
4854
  lastSubmittedRef.current = null;
4298
4855
  try {
4299
4856
  await onResend(prompt.userActionId);
4300
4857
  startResendCooldown();
4301
4858
  } catch {
4302
- setHiddenAfterResend(false);
4303
4859
  }
4304
4860
  }, [locked, onResend, prompt.userActionId, resendSec, startResendCooldown]);
4305
4861
  const handleCancel = useCallback(() => {
@@ -4307,7 +4863,6 @@ function VerificationInline({
4307
4863
  void onCancel(prompt.userActionId);
4308
4864
  }, [busy, onCancel, prompt.userActionId]);
4309
4865
  const description = prompt.message?.trim() || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
4310
- if (hiddenAfterResend) return null;
4311
4866
  return /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua", role: "group", "aria-label": "Verification required", children: [
4312
4867
  /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-head", children: [
4313
4868
  /* @__PURE__ */ jsx(ShieldCheck, { className: "payman-v2-ua-icon", size: 15, strokeWidth: 1.75, "aria-hidden": true }),
@@ -4403,9 +4958,6 @@ function SchemaFormInline({
4403
4958
  const busy = status === "submitting";
4404
4959
  const stale = status === "stale";
4405
4960
  const locked = busy || stale || expired;
4406
- const isUserConfirmation = prompt.subAction === "UserConfirmation";
4407
- const messageFormat = prompt.metadata?.["payman/messageFormat"];
4408
- const renderMarkdown = messageFormat === "markdown";
4409
4961
  const setValue = (key, value) => {
4410
4962
  setValues((prev) => ({ ...prev, [key]: value }));
4411
4963
  setErrors((prev) => {
@@ -4431,8 +4983,8 @@ function SchemaFormInline({
4431
4983
  /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-title", children: "Action required" }),
4432
4984
  typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
4433
4985
  ] }),
4434
- prompt.message?.trim() && (renderMarkdown ? /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-markdown", children: /* @__PURE__ */ jsx(MarkdownRendererV2, { content: prompt.message }) }) : /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: prompt.message })),
4435
- stale ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : fields.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-form", children: fields.map(([key, field]) => {
4986
+ prompt.message?.trim() && /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: prompt.message }),
4987
+ stale ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-stale", children: "This request is no longer available." }) : fields.length === 0 ? /* @__PURE__ */ jsx("p", { className: "payman-v2-ua-desc", children: "This action has no inputs to fill." }) : /* @__PURE__ */ jsx("div", { className: "payman-v2-ua-form", children: fields.map(([key, field]) => {
4436
4988
  const widget = classifyField(field);
4437
4989
  const label = field.title || key;
4438
4990
  const required = isRequired(schema, key);
@@ -4505,7 +5057,7 @@ function SchemaFormInline({
4505
5057
  className: "payman-v2-ua-btn payman-v2-ua-btn-primary",
4506
5058
  disabled: locked,
4507
5059
  onClick: handleSubmit,
4508
- children: busy ? "Submitting\u2026" : isUserConfirmation ? "Confirm" : "Submit"
5060
+ children: busy ? "Submitting\u2026" : "Submit"
4509
5061
  }
4510
5062
  ),
4511
5063
  /* @__PURE__ */ jsx(
@@ -4561,25 +5113,10 @@ function useExpiryCountdown(prompt) {
4561
5113
  const expired = initial !== void 0 && secondsLeft === 0;
4562
5114
  return [secondsLeft, expired];
4563
5115
  }
4564
- function UserActionInline({
4565
- prompt,
4566
- onSubmit,
4567
- onCancel,
4568
- onResend,
4569
- onExpired
4570
- }) {
5116
+ function UserActionInline({ prompt, onSubmit, onCancel, onResend }) {
4571
5117
  const [secondsLeft, expired] = useExpiryCountdown(prompt);
4572
- useEffect(() => {
4573
- if (expired && prompt.kind !== "notification") onExpired?.();
4574
- }, [expired, onExpired, prompt.kind]);
4575
5118
  let body;
4576
- if (expired && prompt.kind !== "notification") {
4577
- const note = {
4578
- id: `${prompt.userActionId}-expired`,
4579
- message: prompt.kind === "verification" ? "Verification Request Expired" : "User Form Expired"
4580
- };
4581
- body = /* @__PURE__ */ jsx(NotificationInline, { notification: note });
4582
- } else if (prompt.kind === "verification") {
5119
+ if (prompt.kind === "verification") {
4583
5120
  body = /* @__PURE__ */ jsx(
4584
5121
  VerificationInline,
4585
5122
  {
@@ -4619,23 +5156,10 @@ function UserActionInline({
4619
5156
  }
4620
5157
  var SCROLL_THRESHOLD2 = 100;
4621
5158
  var USER_SCROLL_UP_EPSILON = 4;
4622
- var PROMPT_KEY_SEPARATOR = "";
4623
- function getPromptSlotKey(prompt) {
4624
- return prompt.toolCallId || prompt.userActionId;
4625
- }
4626
- function getPromptViewKey(prompt) {
4627
- return [
4628
- getPromptSlotKey(prompt),
4629
- prompt.userActionId,
4630
- prompt.subAction ?? "",
4631
- prompt.expirySeconds ?? "",
4632
- prompt.message ?? ""
4633
- ].join(PROMPT_KEY_SEPARATOR);
4634
- }
4635
5159
  var MessageListV2 = forwardRef(
4636
5160
  function MessageListV22({
4637
5161
  messages,
4638
- isStreaming = false,
5162
+ isStreaming: _isStreaming = false,
4639
5163
  onEditUserMessage,
4640
5164
  onRetryUserMessage,
4641
5165
  onImageClick,
@@ -4647,10 +5171,10 @@ var MessageListV2 = forwardRef(
4647
5171
  onSubmitUserAction,
4648
5172
  onCancelUserAction,
4649
5173
  onResendUserAction,
4650
- onExpireUserAction,
4651
5174
  onDismissNotification,
4652
5175
  onSubmitFeedback,
4653
- typingSpeed = 4
5176
+ typingSpeed = 4,
5177
+ sidePanelOpen = false
4654
5178
  }, ref) {
4655
5179
  const noop = useCallback(async () => {
4656
5180
  }, []);
@@ -4658,11 +5182,11 @@ var MessageListV2 = forwardRef(
4658
5182
  const scrollInnerRef = useRef(null);
4659
5183
  const isNearBottomRef = useRef(true);
4660
5184
  const [showScrollBtn, setShowScrollBtn] = useState(false);
4661
- const [expiredPromptViewState, setExpiredPromptViewState] = useState({});
4662
- const expiredUserActionIdsRef = useRef(/* @__PURE__ */ new Set());
4663
5185
  const prevCountRef = useRef(messages.length);
4664
5186
  const pauseStickUntilUserMessageRef = useRef(false);
4665
5187
  const followingBottomRef = useRef(true);
5188
+ const sidePanelOpenRef = useRef(sidePanelOpen);
5189
+ sidePanelOpenRef.current = sidePanelOpen;
4666
5190
  const lastPinAtRef = useRef(0);
4667
5191
  const prevScrollTopRef = useRef(0);
4668
5192
  const getDistanceFromBottom = useCallback(() => {
@@ -4670,92 +5194,6 @@ var MessageListV2 = forwardRef(
4670
5194
  if (!el) return 0;
4671
5195
  return el.scrollHeight - el.scrollTop - el.clientHeight;
4672
5196
  }, []);
4673
- const messageActivityFingerprint = useMemo(() => {
4674
- const last = messages[messages.length - 1];
4675
- const promptFingerprint = (userActionPrompts ?? []).map((prompt) => `${getPromptViewKey(prompt)}:${prompt.status}`).join(PROMPT_KEY_SEPARATOR);
4676
- const notificationFingerprint = (notifications ?? []).map((notification) => notification.id).join(PROMPT_KEY_SEPARATOR);
4677
- if (!last) {
4678
- return [
4679
- 0,
4680
- isStreaming ? "streaming" : "idle",
4681
- promptFingerprint,
4682
- notificationFingerprint
4683
- ].join(PROMPT_KEY_SEPARATOR);
4684
- }
4685
- return [
4686
- messages.length,
4687
- isStreaming ? "streaming" : "idle",
4688
- last.id,
4689
- last.role,
4690
- last.content ?? "",
4691
- last.isStreaming ? "streaming" : "done",
4692
- last.streamProgress ?? "",
4693
- last.steps?.length ?? 0,
4694
- last.errorDetails ?? "",
4695
- promptFingerprint,
4696
- notificationFingerprint
4697
- ].join(PROMPT_KEY_SEPARATOR);
4698
- }, [isStreaming, messages, notifications, userActionPrompts]);
4699
- const handleUserActionExpired = useCallback(
4700
- (promptKey, userActionId) => {
4701
- setExpiredPromptViewState((prev) => {
4702
- if (prev[promptKey]) return prev;
4703
- return {
4704
- ...prev,
4705
- [promptKey]: { baseline: messageActivityFingerprint, hidden: false }
4706
- };
4707
- });
4708
- if (!expiredUserActionIdsRef.current.has(userActionId)) {
4709
- expiredUserActionIdsRef.current.add(userActionId);
4710
- void onExpireUserAction?.(userActionId)?.catch(() => {
4711
- });
4712
- }
4713
- },
4714
- [messageActivityFingerprint, onExpireUserAction]
4715
- );
4716
- useEffect(() => {
4717
- setExpiredPromptViewState((prev) => {
4718
- let changed = false;
4719
- const next = {};
4720
- for (const [key, state] of Object.entries(prev)) {
4721
- if (!state.hidden && state.baseline !== messageActivityFingerprint) {
4722
- next[key] = { ...state, hidden: true };
4723
- changed = true;
4724
- } else {
4725
- next[key] = state;
4726
- }
4727
- }
4728
- return changed ? next : prev;
4729
- });
4730
- }, [messageActivityFingerprint]);
4731
- useEffect(() => {
4732
- const livePromptKeys = new Set((userActionPrompts ?? []).map(getPromptViewKey));
4733
- const liveUserActionIds = new Set((userActionPrompts ?? []).map((p) => p.userActionId));
4734
- for (const userActionId of expiredUserActionIdsRef.current) {
4735
- if (!liveUserActionIds.has(userActionId)) {
4736
- expiredUserActionIdsRef.current.delete(userActionId);
4737
- }
4738
- }
4739
- setExpiredPromptViewState((prev) => {
4740
- let changed = false;
4741
- const next = {};
4742
- for (const [key, state] of Object.entries(prev)) {
4743
- if (livePromptKeys.has(key)) {
4744
- next[key] = state;
4745
- } else {
4746
- changed = true;
4747
- }
4748
- }
4749
- return changed ? next : prev;
4750
- });
4751
- }, [userActionPrompts]);
4752
- const visibleUserActionPrompts = useMemo(
4753
- () => userActionPrompts?.filter((prompt) => {
4754
- const promptKey = getPromptViewKey(prompt);
4755
- return !expiredPromptViewState[promptKey]?.hidden;
4756
- }),
4757
- [expiredPromptViewState, userActionPrompts]
4758
- );
4759
5197
  const pinToBottom = useCallback((behavior = "instant") => {
4760
5198
  const el = scrollRef.current;
4761
5199
  if (!el) return;
@@ -4796,17 +5234,25 @@ var MessageListV2 = forwardRef(
4796
5234
  const nearBottom = distance <= SCROLL_THRESHOLD2;
4797
5235
  isNearBottomRef.current = nearBottom;
4798
5236
  setShowScrollBtn(!nearBottom);
4799
- const sincePin = performance.now() - lastPinAtRef.current;
4800
- if (sincePin < 250) return;
4801
5237
  const scrolledUp = currentScrollTop < prevScrollTop - USER_SCROLL_UP_EPSILON;
4802
5238
  if (scrolledUp) {
4803
5239
  followingBottomRef.current = false;
4804
5240
  pauseStickUntilUserMessageRef.current = true;
4805
- } else if (nearBottom) {
5241
+ return;
5242
+ }
5243
+ const sincePin = performance.now() - lastPinAtRef.current;
5244
+ if (sincePin < 250) return;
5245
+ if (nearBottom) {
4806
5246
  followingBottomRef.current = true;
4807
5247
  pauseStickUntilUserMessageRef.current = false;
4808
5248
  }
4809
5249
  }, [getDistanceFromBottom]);
5250
+ const handleWheel = useCallback((e) => {
5251
+ if (e.deltaY < 0) {
5252
+ followingBottomRef.current = false;
5253
+ pauseStickUntilUserMessageRef.current = true;
5254
+ }
5255
+ }, []);
4810
5256
  useEffect(() => {
4811
5257
  const prevCount = prevCountRef.current;
4812
5258
  prevCountRef.current = messages.length;
@@ -4816,7 +5262,7 @@ var MessageListV2 = forwardRef(
4816
5262
  pauseStickUntilUserMessageRef.current = false;
4817
5263
  followingBottomRef.current = true;
4818
5264
  requestAnimationFrame(() => scrollToBottom());
4819
- } else if (!pauseStickUntilUserMessageRef.current && followingBottomRef.current) {
5265
+ } else if (!sidePanelOpenRef.current && !pauseStickUntilUserMessageRef.current && followingBottomRef.current) {
4820
5266
  requestAnimationFrame(() => scrollToBottom("instant"));
4821
5267
  }
4822
5268
  }
@@ -4825,27 +5271,16 @@ var MessageListV2 = forwardRef(
4825
5271
  const inner = scrollInnerRef.current;
4826
5272
  if (!inner) return;
4827
5273
  const pinIfFollowing = () => {
5274
+ if (sidePanelOpenRef.current) return;
4828
5275
  if (pauseStickUntilUserMessageRef.current) return;
4829
5276
  if (!followingBottomRef.current) return;
5277
+ if (getDistanceFromBottom() > SCROLL_THRESHOLD2) return;
4830
5278
  pinToBottom("instant");
4831
5279
  };
4832
- const ro = new ResizeObserver(() => {
4833
- pinIfFollowing();
4834
- });
5280
+ const ro = new ResizeObserver(pinIfFollowing);
4835
5281
  ro.observe(inner);
4836
- const mo = new MutationObserver(() => {
4837
- pinIfFollowing();
4838
- });
4839
- mo.observe(inner, {
4840
- childList: true,
4841
- subtree: true,
4842
- characterData: true
4843
- });
4844
- return () => {
4845
- ro.disconnect();
4846
- mo.disconnect();
4847
- };
4848
- }, [pinToBottom]);
5282
+ return () => ro.disconnect();
5283
+ }, [pinToBottom, getDistanceFromBottom]);
4849
5284
  useEffect(() => {
4850
5285
  if (messages.length > 0) {
4851
5286
  setTimeout(() => scrollToBottom("instant"), 50);
@@ -4857,6 +5292,7 @@ var MessageListV2 = forwardRef(
4857
5292
  {
4858
5293
  ref: scrollRef,
4859
5294
  onScroll: handleScroll,
5295
+ onWheel: handleWheel,
4860
5296
  className: "payman-v2-message-scroll payman-v2-scrollbar",
4861
5297
  children: /* @__PURE__ */ jsxs(
4862
5298
  "div",
@@ -4870,6 +5306,7 @@ var MessageListV2 = forwardRef(
4870
5306
  message,
4871
5307
  onEdit: onEditUserMessage,
4872
5308
  onRetry: onRetryUserMessage,
5309
+ onImageClick,
4873
5310
  retryDisabled,
4874
5311
  actions: messageActions?.userMessageActions
4875
5312
  }
@@ -4892,20 +5329,16 @@ var MessageListV2 = forwardRef(
4892
5329
  },
4893
5330
  note.id
4894
5331
  )),
4895
- visibleUserActionPrompts?.map((prompt) => {
4896
- const promptKey = getPromptViewKey(prompt);
4897
- return /* @__PURE__ */ jsx(
4898
- UserActionInline,
4899
- {
4900
- prompt,
4901
- onSubmit: onSubmitUserAction ?? noop,
4902
- onCancel: onCancelUserAction ?? noop,
4903
- onResend: onResendUserAction ?? noop,
4904
- onExpired: () => handleUserActionExpired(promptKey, prompt.userActionId)
4905
- },
4906
- promptKey
4907
- );
4908
- })
5332
+ userActionPrompts?.map((prompt) => /* @__PURE__ */ jsx(
5333
+ UserActionInline,
5334
+ {
5335
+ prompt,
5336
+ onSubmit: onSubmitUserAction ?? noop,
5337
+ onCancel: onCancelUserAction ?? noop,
5338
+ onResend: onResendUserAction ?? noop
5339
+ },
5340
+ prompt.toolCallId || prompt.userActionId
5341
+ ))
4909
5342
  ]
4910
5343
  }
4911
5344
  )
@@ -4935,29 +5368,146 @@ var MessageListV2 = forwardRef(
4935
5368
  ] });
4936
5369
  }
4937
5370
  );
4938
- var ChatInputV2 = forwardRef(
4939
- function ChatInputV22({
4940
- onSend,
4941
- disabled = false,
4942
- isStreaming = false,
4943
- placeholder = "Reply...",
4944
- enableVoice = false,
4945
- voiceAvailable = false,
4946
- isRecording = false,
4947
- onVoicePress,
4948
- transcribedText = "",
4949
- showResetSession = false,
4950
- onResetSession,
4951
- showAttachmentButton = true,
4952
- showUploadImageButton = true,
4953
- showAttachFileButton = true,
4954
- onUploadImageClick,
4955
- onAttachFileClick,
4956
- editingMessageId = null,
4957
- onClearEditing,
4958
- analysisMode,
4959
- onAnalysisModeChange,
4960
- slashCommands = []
5371
+ function FilePreviewModal({ src, name, onClose }) {
5372
+ const [isMounted, setIsMounted] = useState(false);
5373
+ const [isLoaded, setIsLoaded] = useState(false);
5374
+ useEffect(() => {
5375
+ setIsMounted(true);
5376
+ return () => setIsMounted(false);
5377
+ }, []);
5378
+ useEffect(() => {
5379
+ setIsLoaded(false);
5380
+ }, [src]);
5381
+ const handleKeyDown = useCallback(
5382
+ (e) => {
5383
+ if (e.key === "Escape") onClose();
5384
+ },
5385
+ [onClose]
5386
+ );
5387
+ useEffect(() => {
5388
+ if (!src || typeof document === "undefined") return;
5389
+ document.addEventListener("keydown", handleKeyDown);
5390
+ const prev = document.body.style.overflow;
5391
+ document.body.style.overflow = "hidden";
5392
+ return () => {
5393
+ document.removeEventListener("keydown", handleKeyDown);
5394
+ document.body.style.overflow = prev;
5395
+ };
5396
+ }, [src, handleKeyDown]);
5397
+ if (!isMounted || typeof document === "undefined") return null;
5398
+ return createPortal(
5399
+ /* @__PURE__ */ jsx(AnimatePresence, { children: src ? /* @__PURE__ */ jsxs(
5400
+ motion.div,
5401
+ {
5402
+ className: "payman-v2-file-preview-overlay",
5403
+ initial: { opacity: 0 },
5404
+ animate: { opacity: 1 },
5405
+ exit: { opacity: 0 },
5406
+ transition: { duration: 0.18 },
5407
+ onClick: onClose,
5408
+ role: "dialog",
5409
+ "aria-modal": "true",
5410
+ "aria-label": `Preview: ${name}`,
5411
+ children: [
5412
+ /* @__PURE__ */ jsx(
5413
+ "button",
5414
+ {
5415
+ type: "button",
5416
+ className: "payman-v2-file-preview-close",
5417
+ "aria-label": "Close preview",
5418
+ onClick: (e) => {
5419
+ e.stopPropagation();
5420
+ onClose();
5421
+ },
5422
+ children: /* @__PURE__ */ jsx(X, { size: 18, strokeWidth: 2 })
5423
+ }
5424
+ ),
5425
+ /* @__PURE__ */ jsx(
5426
+ motion.div,
5427
+ {
5428
+ className: "payman-v2-file-preview-inner",
5429
+ initial: { scale: 0.93, opacity: 0 },
5430
+ animate: { scale: isLoaded ? 1 : 0.93, opacity: isLoaded ? 1 : 0 },
5431
+ exit: { scale: 0.93, opacity: 0 },
5432
+ transition: { duration: 0.2 },
5433
+ onClick: (e) => e.stopPropagation(),
5434
+ children: /* @__PURE__ */ jsx(
5435
+ "img",
5436
+ {
5437
+ src,
5438
+ alt: name,
5439
+ className: "payman-v2-file-preview-img",
5440
+ draggable: false,
5441
+ onLoad: () => setIsLoaded(true)
5442
+ }
5443
+ )
5444
+ }
5445
+ )
5446
+ ]
5447
+ },
5448
+ "file-preview"
5449
+ ) : null }),
5450
+ document.body
5451
+ );
5452
+ }
5453
+ function normalizeExtension(ext) {
5454
+ return ext.replace(/^\./, "").toLowerCase();
5455
+ }
5456
+ function extOf(filename) {
5457
+ return filename.split(".").pop()?.toLowerCase() ?? "";
5458
+ }
5459
+ function isAllowedImage(file, allowedExtensions) {
5460
+ const allowed = new Set(allowedExtensions.map(normalizeExtension));
5461
+ const ext = extOf(file.name);
5462
+ if (allowed.has(ext)) return true;
5463
+ if (!file.type.startsWith("image/")) return false;
5464
+ const mimeSubtype = file.type.slice("image/".length).toLowerCase();
5465
+ return allowed.has(mimeSubtype) || mimeSubtype === "jpeg" && allowed.has("jpg");
5466
+ }
5467
+ function isAllowedDocument(file, allowedExtensions) {
5468
+ const allowed = new Set(allowedExtensions.map(normalizeExtension));
5469
+ return allowed.has(extOf(file.name));
5470
+ }
5471
+ function fileTypeLabel(file, kind) {
5472
+ if (kind === "image") return "Image";
5473
+ const ext = extOf(file.name);
5474
+ return ext ? ext.toUpperCase() : "Document";
5475
+ }
5476
+ var ChatInputV2 = forwardRef(
5477
+ function ChatInputV22({
5478
+ onSend,
5479
+ disabled = false,
5480
+ isStreaming = false,
5481
+ isUploadingAttachments = false,
5482
+ attachmentsReady = true,
5483
+ hasAttachmentUploadErrors = false,
5484
+ attachmentUploadStatusById = {},
5485
+ uploadedAttachmentPayloads = [],
5486
+ placeholder = "Reply...",
5487
+ enableVoice = false,
5488
+ voiceAvailable = false,
5489
+ isRecording = false,
5490
+ onVoicePress,
5491
+ transcribedText = "",
5492
+ showResetSession = false,
5493
+ onResetSession,
5494
+ showAttachmentButton = true,
5495
+ showUploadImageButton = true,
5496
+ showAttachFileButton = true,
5497
+ onUploadImageClick,
5498
+ onAttachFileClick,
5499
+ allowedImageExtensions = ["png", "jpg", "jpeg", "gif", "webp"],
5500
+ allowedFileExtensions = ["pdf", "docx", "xlsx", "xls"],
5501
+ maxCount,
5502
+ maxFileBytes,
5503
+ maxTotalBytes,
5504
+ onFilesChange,
5505
+ onAttachmentsChange,
5506
+ editingMessageId = null,
5507
+ onClearEditing,
5508
+ analysisMode,
5509
+ onAnalysisModeChange,
5510
+ slashCommands = []
4961
5511
  }, ref) {
4962
5512
  const [value, setValue] = useState("");
4963
5513
  const [isFocused, setIsFocused] = useState(false);
@@ -4966,14 +5516,133 @@ var ChatInputV2 = forwardRef(
4966
5516
  const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
4967
5517
  const [inlineHint, setInlineHint] = useState(null);
4968
5518
  const [commandMenuDismissed, setCommandMenuDismissed] = useState(false);
5519
+ const [attachedFiles, setAttachedFiles] = useState([]);
5520
+ const [previewFile, setPreviewFile] = useState(null);
5521
+ const [isSending, setIsSending] = useState(false);
4969
5522
  const textareaRef = useRef(null);
4970
5523
  const actionsRef = useRef(null);
5524
+ const imageInputRef = useRef(null);
5525
+ const fileInputRef = useRef(null);
4971
5526
  const preRecordTextRef = useRef("");
4972
5527
  const voiceTooltipTimerRef = useRef(
4973
5528
  null
4974
5529
  );
4975
5530
  const voiceDraftSyncActiveRef = useRef(false);
4976
- const isInputLocked = disabled || isRecording;
5531
+ const chatContext = useContext(PaymanChatContext);
5532
+ useEffect(() => {
5533
+ return () => {
5534
+ attachedFiles.forEach((f) => URL.revokeObjectURL(f.objectUrl));
5535
+ };
5536
+ }, []);
5537
+ const notifyAttachmentList = useCallback(
5538
+ (files) => {
5539
+ onFilesChange?.(files.map((f) => f.file));
5540
+ onAttachmentsChange?.(files.map((f) => ({ id: f.id, file: f.file })));
5541
+ },
5542
+ [onAttachmentsChange, onFilesChange]
5543
+ );
5544
+ const clearAttachmentsFromInput = useCallback(() => {
5545
+ setAttachedFiles((prev) => {
5546
+ prev.forEach((f) => URL.revokeObjectURL(f.objectUrl));
5547
+ return [];
5548
+ });
5549
+ setPreviewFile(null);
5550
+ onFilesChange?.([]);
5551
+ onAttachmentsChange?.([]);
5552
+ }, [onAttachmentsChange, onFilesChange]);
5553
+ const addFiles = useCallback(
5554
+ (incoming, source) => {
5555
+ const isImage = source === "image";
5556
+ const allowedExtensions = isImage ? allowedImageExtensions : allowedFileExtensions;
5557
+ const isAllowed = isImage ? isAllowedImage : isAllowedDocument;
5558
+ const kindLabel = isImage ? "image" : "document";
5559
+ setAttachedFiles((prev) => {
5560
+ const accepted = [];
5561
+ let rejectedType = false;
5562
+ let hitCountLimit = false;
5563
+ let hitFileSizeLimit = false;
5564
+ let hitTotalSizeLimit = false;
5565
+ let oversizedName = "";
5566
+ const currentTotalBytes = prev.reduce(
5567
+ (sum, item) => sum + item.file.size,
5568
+ 0
5569
+ );
5570
+ let addedBytes = 0;
5571
+ for (const file of incoming) {
5572
+ if (!isAllowed(file, allowedExtensions)) {
5573
+ rejectedType = true;
5574
+ continue;
5575
+ }
5576
+ if (maxFileBytes != null && file.size > maxFileBytes) {
5577
+ hitFileSizeLimit = true;
5578
+ oversizedName = file.name;
5579
+ continue;
5580
+ }
5581
+ if (maxTotalBytes != null && currentTotalBytes + addedBytes + file.size > maxTotalBytes) {
5582
+ hitTotalSizeLimit = true;
5583
+ break;
5584
+ }
5585
+ if (maxCount != null && prev.length + accepted.length >= maxCount) {
5586
+ hitCountLimit = true;
5587
+ break;
5588
+ }
5589
+ accepted.push({
5590
+ id: `${Date.now()}-${Math.random()}`,
5591
+ file,
5592
+ kind: source,
5593
+ objectUrl: URL.createObjectURL(file)
5594
+ });
5595
+ addedBytes += file.size;
5596
+ }
5597
+ if (rejectedType) {
5598
+ setInlineHint(
5599
+ `Unsupported ${kindLabel} type. Allowed: ${allowedExtensions.map(normalizeExtension).join(", ")}.`
5600
+ );
5601
+ } else if (hitFileSizeLimit) {
5602
+ setInlineHint(
5603
+ maxFileBytes != null ? `"${oversizedName}" exceeds the ${formatAttachmentBytes(maxFileBytes)} per-file limit.` : "File exceeds the maximum allowed size."
5604
+ );
5605
+ } else if (hitTotalSizeLimit) {
5606
+ setInlineHint(
5607
+ maxTotalBytes != null ? `Total attachment size would exceed ${formatAttachmentBytes(maxTotalBytes)}.` : "Total attachment size exceeds the limit."
5608
+ );
5609
+ } else if (hitCountLimit) {
5610
+ setInlineHint(
5611
+ maxCount === 1 ? "Only 1 attachment is allowed per message." : `Maximum ${maxCount} attachments allowed per message.`
5612
+ );
5613
+ }
5614
+ if (accepted.length === 0) return prev;
5615
+ const updated = [...prev, ...accepted];
5616
+ notifyAttachmentList(updated);
5617
+ return updated;
5618
+ });
5619
+ },
5620
+ [
5621
+ allowedFileExtensions,
5622
+ allowedImageExtensions,
5623
+ maxCount,
5624
+ maxFileBytes,
5625
+ maxTotalBytes,
5626
+ notifyAttachmentList
5627
+ ]
5628
+ );
5629
+ const removeFile = useCallback(
5630
+ (id) => {
5631
+ setAttachedFiles((prev) => {
5632
+ const target = prev.find((f) => f.id === id);
5633
+ if (target) URL.revokeObjectURL(target.objectUrl);
5634
+ const updated = prev.filter((f) => f.id !== id);
5635
+ notifyAttachmentList(updated);
5636
+ return updated;
5637
+ });
5638
+ setPreviewFile((p) => p?.id === id ? null : p);
5639
+ },
5640
+ [notifyAttachmentList]
5641
+ );
5642
+ const imageAccept = allowedImageExtensions.map((e) => `.${e.replace(/^\./, "")}`).join(",");
5643
+ const fileAccept = allowedFileExtensions.map((e) => `.${e.replace(/^\./, "")}`).join(",");
5644
+ const isInputBusy = isSending || isUploadingAttachments;
5645
+ const isInputLocked = disabled || isRecording || isInputBusy;
4977
5646
  const hasAttachmentOptions = showUploadImageButton || showAttachFileButton;
4978
5647
  const showAttachmentMenuButton = showAttachmentButton && hasAttachmentOptions;
4979
5648
  const showVoiceButton = enableVoice && onVoicePress != null;
@@ -5013,9 +5682,10 @@ var ChatInputV2 = forwardRef(
5013
5682
  const end = message.length;
5014
5683
  textarea.setSelectionRange(end, end);
5015
5684
  });
5016
- }
5685
+ },
5686
+ clearAttachments: clearAttachmentsFromInput
5017
5687
  }),
5018
- [disabled]
5688
+ [disabled, clearAttachmentsFromInput]
5019
5689
  );
5020
5690
  useEffect(() => {
5021
5691
  if (!showActions) return;
@@ -5049,8 +5719,23 @@ var ChatInputV2 = forwardRef(
5049
5719
  const separator = base && !base.endsWith(" ") && transcribedText ? " " : "";
5050
5720
  setValue(`${base}${separator}${transcribedText}`);
5051
5721
  }, [isRecording, transcribedText]);
5052
- const handleSend = useCallback(() => {
5053
- if (!value.trim() || disabled) return;
5722
+ function uploadStatusLabel(status, file, kind) {
5723
+ if (status === "uploading") return "Uploading\u2026";
5724
+ if (status === "error") return "Upload failed";
5725
+ return fileTypeLabel(file, kind);
5726
+ }
5727
+ const handleSend = useCallback(async () => {
5728
+ const hasText = value.trim().length > 0;
5729
+ const hasFiles = attachedFiles.length > 0;
5730
+ if (!hasText && !hasFiles || disabled || isSending || isUploadingAttachments) return;
5731
+ if (hasFiles && !attachmentsReady) {
5732
+ setInlineHint("Waiting for attachments to finish uploading.");
5733
+ return;
5734
+ }
5735
+ if (hasFiles && hasAttachmentUploadErrors) {
5736
+ setInlineHint("Remove or re-add attachments that failed to upload.");
5737
+ return;
5738
+ }
5054
5739
  const commandHint = getSlashCommandValidationHint(value);
5055
5740
  if (commandHint) {
5056
5741
  setInlineHint(commandHint);
@@ -5060,15 +5745,38 @@ var ChatInputV2 = forwardRef(
5060
5745
  preRecordTextRef.current = "";
5061
5746
  setInlineHint(null);
5062
5747
  onClearEditing?.();
5063
- onSend(value.trim());
5748
+ const textToSend = value.trim();
5749
+ const filesToSend = attachedFiles.map((f) => f.file);
5750
+ const attachmentsToSend = [...uploadedAttachmentPayloads];
5064
5751
  setValue("");
5752
+ clearAttachmentsFromInput();
5065
5753
  requestAnimationFrame(() => {
5066
5754
  if (textareaRef.current) {
5067
5755
  textareaRef.current.style.height = "auto";
5068
5756
  textareaRef.current.focus();
5069
5757
  }
5070
5758
  });
5071
- }, [value, disabled, onClearEditing, onSend]);
5759
+ setIsSending(true);
5760
+ try {
5761
+ await onSend(textToSend, filesToSend, attachmentsToSend);
5762
+ } catch {
5763
+ setInlineHint("Failed to send message. Please try again.");
5764
+ } finally {
5765
+ setIsSending(false);
5766
+ }
5767
+ }, [
5768
+ value,
5769
+ attachedFiles,
5770
+ uploadedAttachmentPayloads,
5771
+ disabled,
5772
+ isSending,
5773
+ isUploadingAttachments,
5774
+ attachmentsReady,
5775
+ hasAttachmentUploadErrors,
5776
+ onClearEditing,
5777
+ onSend,
5778
+ clearAttachmentsFromInput
5779
+ ]);
5072
5780
  const selectCommand = useCallback(
5073
5781
  (command) => {
5074
5782
  const insertText = command.insertText ?? `${command.name} `;
@@ -5113,18 +5821,30 @@ var ChatInputV2 = forwardRef(
5113
5821
  }
5114
5822
  if (e.key === "Enter" && !e.shiftKey) {
5115
5823
  e.preventDefault();
5116
- if (isStreaming) return;
5117
- handleSend();
5824
+ if (isStreaming || isSending || isUploadingAttachments) return;
5825
+ void handleSend();
5118
5826
  }
5119
5827
  };
5120
5828
  const handleUploadImageClick = () => {
5829
+ imageInputRef.current?.click();
5121
5830
  onUploadImageClick?.();
5122
5831
  setShowActions(false);
5123
5832
  };
5124
5833
  const handleAttachFileClick = () => {
5834
+ fileInputRef.current?.click();
5125
5835
  onAttachFileClick?.();
5126
5836
  setShowActions(false);
5127
5837
  };
5838
+ const handleImageFilesSelected = (e) => {
5839
+ const files = Array.from(e.target.files ?? []);
5840
+ if (files.length) addFiles(files, "image");
5841
+ e.target.value = "";
5842
+ };
5843
+ const handleDocFilesSelected = (e) => {
5844
+ const files = Array.from(e.target.files ?? []);
5845
+ if (files.length) addFiles(files, "file");
5846
+ e.target.value = "";
5847
+ };
5128
5848
  const hideVoiceTooltip = useCallback(() => {
5129
5849
  if (voiceTooltipTimerRef.current) {
5130
5850
  clearTimeout(voiceTooltipTimerRef.current);
@@ -5152,9 +5872,39 @@ var ChatInputV2 = forwardRef(
5152
5872
  }
5153
5873
  onVoicePress();
5154
5874
  };
5155
- const canSend = value.trim().length > 0 && !disabled;
5156
- const sendDisabled = !canSend || isStreaming;
5875
+ const canSend = (value.trim().length > 0 || attachedFiles.length > 0) && !disabled;
5876
+ const sendDisabled = !canSend || isStreaming || isUploadingAttachments || !attachmentsReady || hasAttachmentUploadErrors || isSending;
5157
5877
  return /* @__PURE__ */ jsxs("div", { className: "payman-v2-input-container", children: [
5878
+ /* @__PURE__ */ jsx(
5879
+ "input",
5880
+ {
5881
+ ref: imageInputRef,
5882
+ type: "file",
5883
+ accept: imageAccept,
5884
+ multiple: maxCount == null || maxCount > 1,
5885
+ style: { display: "none" },
5886
+ onChange: handleImageFilesSelected
5887
+ }
5888
+ ),
5889
+ /* @__PURE__ */ jsx(
5890
+ "input",
5891
+ {
5892
+ ref: fileInputRef,
5893
+ type: "file",
5894
+ accept: fileAccept,
5895
+ multiple: maxCount == null || maxCount > 1,
5896
+ style: { display: "none" },
5897
+ onChange: handleDocFilesSelected
5898
+ }
5899
+ ),
5900
+ /* @__PURE__ */ jsx(
5901
+ FilePreviewModal,
5902
+ {
5903
+ src: previewFile?.kind === "image" ? previewFile.objectUrl : null,
5904
+ name: previewFile?.file.name ?? "",
5905
+ onClose: () => setPreviewFile(null)
5906
+ }
5907
+ ),
5158
5908
  /* @__PURE__ */ jsx(AnimatePresence, { children: showCommandSuggestions && /* @__PURE__ */ jsx(
5159
5909
  motion.div,
5160
5910
  {
@@ -5193,6 +5943,115 @@ var ChatInputV2 = forwardRef(
5193
5943
  ),
5194
5944
  children: [
5195
5945
  /* @__PURE__ */ jsxs("div", { className: "payman-v2-input-body", children: [
5946
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: attachedFiles.length > 0 && /* @__PURE__ */ jsx(
5947
+ motion.div,
5948
+ {
5949
+ initial: { opacity: 0, height: 0 },
5950
+ animate: { opacity: 1, height: "auto" },
5951
+ exit: { opacity: 0, height: 0 },
5952
+ transition: { duration: 0.15, ease: "easeOut" },
5953
+ className: "payman-v2-file-preview",
5954
+ children: attachedFiles.map((af) => {
5955
+ const uploadStatus = attachmentUploadStatusById[af.id];
5956
+ const isUploadingFile = uploadStatus === "uploading";
5957
+ const hasUploadError = uploadStatus === "error";
5958
+ return /* @__PURE__ */ jsxs("div", { className: "payman-v2-file-preview-item", children: [
5959
+ af.kind === "image" ? /* @__PURE__ */ jsxs(
5960
+ "button",
5961
+ {
5962
+ type: "button",
5963
+ className: cn(
5964
+ "payman-v2-file-preview-shell payman-v2-file-preview-shell-clickable",
5965
+ hasUploadError && "payman-v2-file-preview-shell-error"
5966
+ ),
5967
+ onClick: () => setPreviewFile(af),
5968
+ disabled: isUploadingFile,
5969
+ "aria-label": `Preview ${af.file.name}`,
5970
+ children: [
5971
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-file-preview-thumb", children: [
5972
+ /* @__PURE__ */ jsx(
5973
+ "img",
5974
+ {
5975
+ src: af.objectUrl,
5976
+ alt: "",
5977
+ draggable: false
5978
+ }
5979
+ ),
5980
+ isUploadingFile && /* @__PURE__ */ jsx("div", { className: "payman-v2-file-preview-uploading", children: /* @__PURE__ */ jsx(Loader2, { size: 14, className: "payman-v2-spin" }) })
5981
+ ] }),
5982
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-file-preview-info", children: [
5983
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-file-preview-name", children: af.file.name }),
5984
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-file-preview-type", children: uploadStatusLabel(uploadStatus, af.file, af.kind) })
5985
+ ] })
5986
+ ]
5987
+ }
5988
+ ) : isPdfFile(af.file) ? /* @__PURE__ */ jsxs(
5989
+ "button",
5990
+ {
5991
+ type: "button",
5992
+ className: cn(
5993
+ "payman-v2-file-preview-shell payman-v2-file-preview-shell-clickable",
5994
+ hasUploadError && "payman-v2-file-preview-shell-error"
5995
+ ),
5996
+ onClick: () => chatContext?.openPdfSheet(
5997
+ af.objectUrl,
5998
+ af.file.name
5999
+ ),
6000
+ disabled: isUploadingFile,
6001
+ "aria-label": `Preview ${af.file.name}`,
6002
+ children: [
6003
+ /* @__PURE__ */ jsx("div", { className: "payman-v2-file-preview-thumb", children: isUploadingFile ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "payman-v2-spin payman-v2-file-preview-doc-icon" }) : /* @__PURE__ */ jsx(
6004
+ FileText,
6005
+ {
6006
+ size: 16,
6007
+ strokeWidth: 1.75,
6008
+ className: "payman-v2-file-preview-doc-icon"
6009
+ }
6010
+ ) }),
6011
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-file-preview-info", children: [
6012
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-file-preview-name", children: af.file.name }),
6013
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-file-preview-type", children: uploadStatusLabel(uploadStatus, af.file, af.kind) })
6014
+ ] })
6015
+ ]
6016
+ }
6017
+ ) : /* @__PURE__ */ jsxs(
6018
+ "div",
6019
+ {
6020
+ className: cn(
6021
+ "payman-v2-file-preview-shell",
6022
+ hasUploadError && "payman-v2-file-preview-shell-error"
6023
+ ),
6024
+ children: [
6025
+ /* @__PURE__ */ jsx("div", { className: "payman-v2-file-preview-thumb", children: isUploadingFile ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "payman-v2-spin payman-v2-file-preview-doc-icon" }) : /* @__PURE__ */ jsx(
6026
+ FileText,
6027
+ {
6028
+ size: 16,
6029
+ strokeWidth: 1.75,
6030
+ className: "payman-v2-file-preview-doc-icon"
6031
+ }
6032
+ ) }),
6033
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-file-preview-info", children: [
6034
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-file-preview-name", children: af.file.name }),
6035
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-file-preview-type", children: uploadStatusLabel(uploadStatus, af.file, af.kind) })
6036
+ ] })
6037
+ ]
6038
+ }
6039
+ ),
6040
+ /* @__PURE__ */ jsx(
6041
+ "button",
6042
+ {
6043
+ type: "button",
6044
+ className: "payman-v2-file-preview-remove",
6045
+ onClick: () => removeFile(af.id),
6046
+ "aria-label": `Remove ${af.file.name}`,
6047
+ children: /* @__PURE__ */ jsx(X, { size: 12, strokeWidth: 2.5 })
6048
+ }
6049
+ )
6050
+ ] }, af.id);
6051
+ })
6052
+ },
6053
+ "file-preview"
6054
+ ) }),
5196
6055
  /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: editingMessageId && /* @__PURE__ */ jsxs(
5197
6056
  motion.div,
5198
6057
  {
@@ -5424,13 +6283,13 @@ var ChatInputV2 = forwardRef(
5424
6283
  "button",
5425
6284
  {
5426
6285
  type: "button",
5427
- onClick: handleSend,
6286
+ onClick: () => void handleSend(),
5428
6287
  disabled: sendDisabled,
5429
6288
  className: cn(
5430
6289
  "payman-v2-input-send-btn",
5431
6290
  sendDisabled && "payman-v2-input-send-btn-disabled"
5432
6291
  ),
5433
- "aria-label": "Send message",
6292
+ "aria-label": isUploadingAttachments || isSending ? "Uploading attachments" : "Send message",
5434
6293
  children: /* @__PURE__ */ jsx(
5435
6294
  ArrowUp,
5436
6295
  {
@@ -5965,6 +6824,389 @@ function TimelineBars({
5965
6824
  )
5966
6825
  ] });
5967
6826
  }
6827
+
6828
+ // src/utils/pdfPreview.ts
6829
+ var PdfPreviewError = class extends Error {
6830
+ constructor(kind, title, userMessage) {
6831
+ super(userMessage);
6832
+ __publicField(this, "kind");
6833
+ __publicField(this, "title");
6834
+ __publicField(this, "userMessage");
6835
+ this.name = "PdfPreviewError";
6836
+ this.kind = kind;
6837
+ this.title = title;
6838
+ this.userMessage = userMessage;
6839
+ }
6840
+ };
6841
+ function classifyErrorBody(body, status) {
6842
+ const text = body.replace(/\s+/g, " ").trim();
6843
+ if (/signed expiry time|must be after signed start time|expired|expir/i.test(text)) {
6844
+ return new PdfPreviewError(
6845
+ "expired",
6846
+ "Link expired",
6847
+ "This download link has expired. Ask the assistant to generate a new copy of the document."
6848
+ );
6849
+ }
6850
+ if (/AuthenticationFailed|authorization header|formed correctly including the signature/i.test(
6851
+ text
6852
+ )) {
6853
+ return new PdfPreviewError(
6854
+ "forbidden",
6855
+ "Link no longer valid",
6856
+ "This preview link is no longer valid. Request a fresh download link and try again."
6857
+ );
6858
+ }
6859
+ if (status === 404 || /BlobNotFound|ResourceNotFound|The specified blob does not exist/i.test(text)) {
6860
+ return new PdfPreviewError(
6861
+ "not_found",
6862
+ "Document not found",
6863
+ "The file is no longer available. It may have been removed or the link is incorrect."
6864
+ );
6865
+ }
6866
+ if (status === 403) {
6867
+ return new PdfPreviewError(
6868
+ "forbidden",
6869
+ "Can't open document",
6870
+ "You may not have access to this file, or the link has expired."
6871
+ );
6872
+ }
6873
+ if (status === 401) {
6874
+ return new PdfPreviewError(
6875
+ "forbidden",
6876
+ "Access denied",
6877
+ "This preview link could not be verified. Request a new download link."
6878
+ );
6879
+ }
6880
+ return new PdfPreviewError(
6881
+ "unknown",
6882
+ "Can't preview document",
6883
+ "Something went wrong while opening this file. Try downloading it or request a new link."
6884
+ );
6885
+ }
6886
+ function normalizePdfPreviewError(error) {
6887
+ if (error instanceof PdfPreviewError) return error;
6888
+ if (error instanceof TypeError) {
6889
+ return new PdfPreviewError(
6890
+ "network",
6891
+ "Connection problem",
6892
+ "Could not load the document. Check your connection and try again."
6893
+ );
6894
+ }
6895
+ return new PdfPreviewError(
6896
+ "unknown",
6897
+ "Can't preview document",
6898
+ "Something went wrong while opening this file. Try downloading it or request a new link."
6899
+ );
6900
+ }
6901
+ async function assertPdfBlob(blob, status) {
6902
+ if (!blob.size) {
6903
+ return Promise.reject(
6904
+ new PdfPreviewError(
6905
+ "empty",
6906
+ "Document unavailable",
6907
+ "The file response was empty. Request a new download link and try again."
6908
+ )
6909
+ );
6910
+ }
6911
+ const head = await blob.slice(0, Math.min(blob.size, 1024)).text();
6912
+ if (head.startsWith("%PDF")) {
6913
+ return blob;
6914
+ }
6915
+ throw classifyErrorBody(head, status);
6916
+ }
6917
+ async function fetchPdfBlob(src) {
6918
+ let response;
6919
+ try {
6920
+ response = await fetch(src, {
6921
+ method: "GET",
6922
+ headers: { Accept: "application/pdf,*/*" }
6923
+ });
6924
+ } catch {
6925
+ throw new PdfPreviewError(
6926
+ "network",
6927
+ "Connection problem",
6928
+ "Could not load the document. Check your connection and try again."
6929
+ );
6930
+ }
6931
+ const blob = await response.blob();
6932
+ if (!response.ok) {
6933
+ const body = await blob.text().catch(() => "");
6934
+ throw classifyErrorBody(body, response.status);
6935
+ }
6936
+ return assertPdfBlob(blob, response.status);
6937
+ }
6938
+ var MIN_WIDTH = 320;
6939
+ var MAX_WIDTH_RATIO = 0.6;
6940
+ var DEFAULT_WIDTH = 520;
6941
+ var SPRING = { type: "spring", stiffness: 340, damping: 34, mass: 0.85 };
6942
+ var SHEET_EXIT = { duration: 0.22, ease: [0.4, 0, 1, 1] };
6943
+ function pdfDownloadName(title) {
6944
+ const base = title.trim() || "document";
6945
+ return base.toLowerCase().endsWith(".pdf") ? base : `${base}.pdf`;
6946
+ }
6947
+ function clampSplitWidth(w) {
6948
+ const maxW = Math.floor(window.innerWidth * MAX_WIDTH_RATIO);
6949
+ return Math.max(MIN_WIDTH, Math.min(maxW, w));
6950
+ }
6951
+ function PdfSheetV2({
6952
+ src,
6953
+ title,
6954
+ onClose,
6955
+ mode = "split"
6956
+ }) {
6957
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: src && /* @__PURE__ */ jsx(
6958
+ PdfSheetPanel,
6959
+ {
6960
+ src,
6961
+ title,
6962
+ onClose,
6963
+ mode
6964
+ },
6965
+ "pdf-panel"
6966
+ ) });
6967
+ }
6968
+ function PdfSheetPanel({
6969
+ src,
6970
+ title,
6971
+ onClose,
6972
+ mode
6973
+ }) {
6974
+ const isSheet = mode === "sheet";
6975
+ const [isLoaded, setIsLoaded] = useState(false);
6976
+ const [isDownloading, setIsDownloading] = useState(false);
6977
+ const [previewUrl, setPreviewUrl] = useState(null);
6978
+ const [previewError, setPreviewError] = useState(null);
6979
+ const [loadAttempt, setLoadAttempt] = useState(0);
6980
+ const blobRef = useRef(null);
6981
+ const objectUrlRef = useRef(null);
6982
+ const [panelWidth, setPanelWidth] = useState(DEFAULT_WIDTH);
6983
+ const [splitOpened, setSplitOpened] = useState(false);
6984
+ const [isDragging, setIsDragging] = useState(false);
6985
+ useEffect(() => {
6986
+ setIsLoaded(false);
6987
+ setPreviewUrl(null);
6988
+ setPreviewError(null);
6989
+ setPanelWidth(DEFAULT_WIDTH);
6990
+ setSplitOpened(false);
6991
+ blobRef.current = null;
6992
+ if (objectUrlRef.current) {
6993
+ URL.revokeObjectURL(objectUrlRef.current);
6994
+ objectUrlRef.current = null;
6995
+ }
6996
+ let cancelled = false;
6997
+ const loadPreview = async () => {
6998
+ try {
6999
+ const blob = await fetchPdfBlob(src);
7000
+ if (cancelled) return;
7001
+ blobRef.current = blob;
7002
+ const objectUrl = URL.createObjectURL(blob);
7003
+ objectUrlRef.current = objectUrl;
7004
+ setPreviewUrl(objectUrl);
7005
+ } catch (error) {
7006
+ if (!cancelled) {
7007
+ setPreviewError(normalizePdfPreviewError(error));
7008
+ }
7009
+ }
7010
+ };
7011
+ void loadPreview();
7012
+ return () => {
7013
+ cancelled = true;
7014
+ if (objectUrlRef.current) {
7015
+ URL.revokeObjectURL(objectUrlRef.current);
7016
+ objectUrlRef.current = null;
7017
+ }
7018
+ blobRef.current = null;
7019
+ };
7020
+ }, [src, loadAttempt]);
7021
+ useEffect(() => {
7022
+ if (isSheet) return;
7023
+ const onWindowResize = () => setPanelWidth((size) => clampSplitWidth(size));
7024
+ window.addEventListener("resize", onWindowResize);
7025
+ return () => window.removeEventListener("resize", onWindowResize);
7026
+ }, [isSheet]);
7027
+ const handleKeyDown = useCallback(
7028
+ (e) => {
7029
+ if (e.key === "Escape") onClose();
7030
+ },
7031
+ [onClose]
7032
+ );
7033
+ useEffect(() => {
7034
+ document.addEventListener("keydown", handleKeyDown);
7035
+ return () => document.removeEventListener("keydown", handleKeyDown);
7036
+ }, [handleKeyDown]);
7037
+ const canDownload = isLoaded && previewUrl != null && !previewError;
7038
+ const handleDownload = async () => {
7039
+ const filename = pdfDownloadName(title);
7040
+ setIsDownloading(true);
7041
+ try {
7042
+ const blob = blobRef.current ?? await fetchPdfBlob(src);
7043
+ const objectUrl = URL.createObjectURL(blob);
7044
+ const a = document.createElement("a");
7045
+ a.href = objectUrl;
7046
+ a.download = filename;
7047
+ a.target = "_blank";
7048
+ a.rel = "noopener noreferrer";
7049
+ document.body.appendChild(a);
7050
+ a.click();
7051
+ a.remove();
7052
+ URL.revokeObjectURL(objectUrl);
7053
+ } catch (error) {
7054
+ setPreviewError(normalizePdfPreviewError(error));
7055
+ } finally {
7056
+ setIsDownloading(false);
7057
+ }
7058
+ };
7059
+ const handleRetry = () => {
7060
+ setPreviewError(null);
7061
+ setIsLoaded(false);
7062
+ setPreviewUrl(null);
7063
+ setLoadAttempt((attempt) => attempt + 1);
7064
+ };
7065
+ const handleOpenInNewTab = () => {
7066
+ window.open(src, "_blank", "noopener,noreferrer");
7067
+ };
7068
+ const onResizeMouseDown = (e) => {
7069
+ if (e.button !== 0) return;
7070
+ e.preventDefault();
7071
+ const startX = e.clientX;
7072
+ const startW = panelWidth;
7073
+ setIsDragging(true);
7074
+ const onMove = (ev) => {
7075
+ setPanelWidth(clampSplitWidth(startW + (startX - ev.clientX)));
7076
+ };
7077
+ const onUp = () => {
7078
+ setIsDragging(false);
7079
+ document.removeEventListener("mousemove", onMove);
7080
+ document.removeEventListener("mouseup", onUp);
7081
+ };
7082
+ document.addEventListener("mousemove", onMove);
7083
+ document.addEventListener("mouseup", onUp);
7084
+ };
7085
+ return /* @__PURE__ */ jsxs(
7086
+ motion.div,
7087
+ {
7088
+ className: cn(
7089
+ "payman-v2-root payman-v2-pdf-sheet-panel",
7090
+ isSheet && "payman-v2-pdf-sheet-panel--sheet"
7091
+ ),
7092
+ style: { minWidth: 0 },
7093
+ initial: isSheet ? { x: "100%", opacity: 0 } : { width: 0, opacity: 0 },
7094
+ animate: isSheet ? { x: 0, opacity: 1 } : { width: panelWidth, opacity: 1 },
7095
+ exit: isSheet ? { x: "100%", opacity: 0, transition: { x: SHEET_EXIT, opacity: SHEET_EXIT } } : { width: 0, opacity: 0, transition: { width: SHEET_EXIT, opacity: SHEET_EXIT } },
7096
+ transition: isSheet ? { x: SPRING, opacity: SPRING } : {
7097
+ width: splitOpened ? { duration: 0 } : SPRING,
7098
+ opacity: SPRING
7099
+ },
7100
+ onAnimationComplete: () => {
7101
+ if (!isSheet && !splitOpened) setSplitOpened(true);
7102
+ },
7103
+ children: [
7104
+ !isSheet && /* @__PURE__ */ jsx(
7105
+ "div",
7106
+ {
7107
+ className: "payman-v2-pdf-sheet-resize-handle",
7108
+ onMouseDown: onResizeMouseDown,
7109
+ "aria-hidden": "true",
7110
+ children: /* @__PURE__ */ jsx("div", { className: "payman-v2-pdf-sheet-resize-grip" })
7111
+ }
7112
+ ),
7113
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-header", children: [
7114
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-header-left", children: [
7115
+ /* @__PURE__ */ jsx("div", { className: "payman-v2-pdf-sheet-file-icon", children: /* @__PURE__ */ jsx(FileText, { size: 14, strokeWidth: 1.75 }) }),
7116
+ /* @__PURE__ */ jsx("span", { className: "payman-v2-pdf-sheet-title", title, children: title || "Document" })
7117
+ ] }),
7118
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-header-actions", children: [
7119
+ /* @__PURE__ */ jsxs(
7120
+ "button",
7121
+ {
7122
+ type: "button",
7123
+ className: "payman-v2-pdf-sheet-download-btn",
7124
+ "aria-label": "Download PDF",
7125
+ disabled: !canDownload || isDownloading,
7126
+ onClick: () => void handleDownload(),
7127
+ children: [
7128
+ isDownloading ? /* @__PURE__ */ jsx(Loader2, { size: 13, strokeWidth: 2, style: { animation: "payman-v2-spin 0.65s linear infinite" } }) : /* @__PURE__ */ jsx(Download, { size: 13, strokeWidth: 2 }),
7129
+ /* @__PURE__ */ jsx("span", { children: isDownloading ? "Downloading\u2026" : "Download" })
7130
+ ]
7131
+ }
7132
+ ),
7133
+ /* @__PURE__ */ jsx(
7134
+ "button",
7135
+ {
7136
+ type: "button",
7137
+ className: "payman-v2-pdf-sheet-close-btn",
7138
+ "aria-label": "Close preview",
7139
+ onClick: (e) => {
7140
+ e.stopPropagation();
7141
+ onClose();
7142
+ },
7143
+ children: /* @__PURE__ */ jsx(X, { size: 15, strokeWidth: 2.25 })
7144
+ }
7145
+ )
7146
+ ] })
7147
+ ] }),
7148
+ /* @__PURE__ */ jsx("div", { className: "payman-v2-pdf-sheet-body", children: previewError ? /* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-error", role: "alert", children: [
7149
+ /* @__PURE__ */ jsx("div", { className: "payman-v2-pdf-sheet-error-icon", children: /* @__PURE__ */ jsx(AlertCircle, { size: 22, strokeWidth: 1.75 }) }),
7150
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-pdf-sheet-error-title", children: previewError.title }),
7151
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-pdf-sheet-error-message", children: previewError.userMessage }),
7152
+ /* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-error-actions", children: [
7153
+ previewError.kind === "network" && /* @__PURE__ */ jsxs(
7154
+ "button",
7155
+ {
7156
+ type: "button",
7157
+ className: "payman-v2-pdf-sheet-error-btn",
7158
+ onClick: handleRetry,
7159
+ children: [
7160
+ /* @__PURE__ */ jsx(RefreshCw, { size: 14, strokeWidth: 2 }),
7161
+ /* @__PURE__ */ jsx("span", { children: "Try again" })
7162
+ ]
7163
+ }
7164
+ ),
7165
+ (previewError.kind === "network" || previewError.kind === "unknown") && /* @__PURE__ */ jsxs(
7166
+ "button",
7167
+ {
7168
+ type: "button",
7169
+ className: "payman-v2-pdf-sheet-error-btn payman-v2-pdf-sheet-error-btn-secondary",
7170
+ onClick: handleOpenInNewTab,
7171
+ children: [
7172
+ /* @__PURE__ */ jsx(ExternalLink, { size: 14, strokeWidth: 2 }),
7173
+ /* @__PURE__ */ jsx("span", { children: "Open link" })
7174
+ ]
7175
+ }
7176
+ )
7177
+ ] })
7178
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
7179
+ (!isLoaded || !previewUrl) && /* @__PURE__ */ jsxs("div", { className: "payman-v2-pdf-sheet-loading", children: [
7180
+ /* @__PURE__ */ jsx(
7181
+ Loader2,
7182
+ {
7183
+ size: 20,
7184
+ strokeWidth: 2,
7185
+ style: { animation: "payman-v2-spin 0.65s linear infinite", color: "var(--payman-v2-text-3)" }
7186
+ }
7187
+ ),
7188
+ /* @__PURE__ */ jsx("p", { className: "payman-v2-pdf-sheet-loading-text", children: previewUrl ? "Preparing preview\u2026" : "Opening document\u2026" })
7189
+ ] }),
7190
+ previewUrl && /* @__PURE__ */ jsx(
7191
+ "iframe",
7192
+ {
7193
+ src: previewUrl,
7194
+ title: title || "PDF Preview",
7195
+ className: "payman-v2-pdf-sheet-iframe",
7196
+ style: {
7197
+ opacity: isLoaded ? 1 : 0,
7198
+ transition: "opacity 0.3s ease",
7199
+ pointerEvents: isDragging ? "none" : "auto"
7200
+ },
7201
+ onLoad: () => setIsLoaded(true)
7202
+ },
7203
+ previewUrl
7204
+ )
7205
+ ] }) })
7206
+ ]
7207
+ }
7208
+ );
7209
+ }
5968
7210
  var DEFAULT_USER_ACTION_STATE = {
5969
7211
  prompts: [],
5970
7212
  notifications: []
@@ -6059,7 +7301,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6059
7301
  onLoadMoreMessages,
6060
7302
  isLoadingMoreMessages = false,
6061
7303
  hasMoreMessages = false,
6062
- chat
7304
+ chat,
7305
+ attachmentUpload
6063
7306
  }, ref) {
6064
7307
  const [inputValue, setInputValue] = useState("");
6065
7308
  const prevInputValueRef = useRef(inputValue);
@@ -6113,7 +7356,6 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6113
7356
  const submitUserAction2 = chat.submitUserAction ?? NOOP_ASYNC;
6114
7357
  const cancelUserAction2 = chat.cancelUserAction ?? NOOP_ASYNC;
6115
7358
  const resendUserAction2 = chat.resendUserAction ?? NOOP_ASYNC;
6116
- const expireUserAction2 = chat.expireUserAction ?? NOOP_ASYNC;
6117
7359
  const dismissNotification = chat.dismissNotification ?? NOOP;
6118
7360
  const isUserActionSupported = typeof chat.submitUserAction === "function" && typeof chat.cancelUserAction === "function" && typeof chat.resendUserAction === "function";
6119
7361
  const {
@@ -6139,6 +7381,19 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6139
7381
  }
6140
7382
  }
6141
7383
  );
7384
+ const [pdfSheet, setPdfSheet] = useState(null);
7385
+ const autoOpenedPdfHrefsRef = useRef(/* @__PURE__ */ new Set());
7386
+ const pdfPreviewMode = config.pdfPreviewMode ?? "split";
7387
+ const openPdfSheet = useCallback((href, title, options) => {
7388
+ if (options?.auto) {
7389
+ if (autoOpenedPdfHrefsRef.current.has(href)) return;
7390
+ autoOpenedPdfHrefsRef.current.add(href);
7391
+ }
7392
+ setPdfSheet({ href, title });
7393
+ }, []);
7394
+ const closePdfSheet = useCallback(() => {
7395
+ setPdfSheet(null);
7396
+ }, []);
6142
7397
  const contextValue = useMemo(
6143
7398
  () => ({
6144
7399
  resetSession,
@@ -6147,7 +7402,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6147
7402
  cancelStream,
6148
7403
  getSessionId,
6149
7404
  getMessages,
6150
- isWaitingForResponse
7405
+ isWaitingForResponse,
7406
+ openPdfSheet
6151
7407
  }),
6152
7408
  [
6153
7409
  resetSession,
@@ -6156,7 +7412,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6156
7412
  cancelStream,
6157
7413
  getSessionId,
6158
7414
  getMessages,
6159
- isWaitingForResponse
7415
+ isWaitingForResponse,
7416
+ openPdfSheet
6160
7417
  ]
6161
7418
  );
6162
7419
  const {
@@ -6212,7 +7469,10 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6212
7469
  setInputValue("");
6213
7470
  setLightboxSrc(null);
6214
7471
  setLightboxAlt("");
7472
+ setPdfSheet(null);
7473
+ autoOpenedPdfHrefsRef.current.clear();
6215
7474
  chatInputV2Ref.current?.setDraft("");
7475
+ chatInputV2Ref.current?.clearAttachments();
6216
7476
  clearTranscript();
6217
7477
  if (isRecording) {
6218
7478
  stopRecording();
@@ -6263,14 +7523,20 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6263
7523
  emptyStateComponent,
6264
7524
  showResetSession = false,
6265
7525
  enableDeepModeToggle = true,
6266
- showAttachmentButton = true,
6267
- showUploadImageButton = true,
6268
- showAttachFileButton = true,
6269
7526
  messageActions: messageActionsConfig,
6270
7527
  enableSlashCommands = true,
6271
7528
  slashCommands: slashCommandsConfig,
6272
7529
  commandPermissions
6273
7530
  } = config;
7531
+ const attachmentSettings = useMemo(
7532
+ () => resolveChatAttachmentConfig(config),
7533
+ [
7534
+ config.attachments,
7535
+ config.showAttachmentButton,
7536
+ config.showUploadImageButton,
7537
+ config.showAttachFileButton
7538
+ ]
7539
+ );
6274
7540
  const messageActions = useMemo(
6275
7541
  () => ({
6276
7542
  userMessageActions: {
@@ -6358,11 +7624,22 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6358
7624
  };
6359
7625
  const userActionPrompts = isUserActionSupported ? userActionState.prompts : void 0;
6360
7626
  const notifications = userActionState.notifications;
6361
- const handleV2Send = (text) => {
7627
+ const handleAttachmentsChange = useCallback(
7628
+ (attachments) => {
7629
+ attachmentUpload.syncAttachments(attachments);
7630
+ },
7631
+ [attachmentUpload]
7632
+ );
7633
+ const handleV2Send = (text, files = [], attachments = []) => {
6362
7634
  if (isRecording) stopRecording();
6363
- if (text.trim() && !disableInput && isSessionParamsConfigured) {
7635
+ if ((text.trim() || files.length > 0) && !disableInput && isSessionParamsConfigured) {
7636
+ if (files.length > 0 && attachments.length === 0) return;
6364
7637
  setEditingMessageId(null);
6365
- void sendMessage(text.trim(), { analysisMode: effectiveAnalysisMode });
7638
+ void sendMessage(text.trim(), {
7639
+ analysisMode: effectiveAnalysisMode,
7640
+ attachments,
7641
+ files
7642
+ });
6366
7643
  }
6367
7644
  };
6368
7645
  const handleVoicePress = useCallback(async () => {
@@ -6410,156 +7687,214 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6410
7687
  style,
6411
7688
  children: [
6412
7689
  children,
6413
- /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: isEmpty && !hasEverSentMessage ? /* @__PURE__ */ jsx(
6414
- motion.div,
7690
+ /* @__PURE__ */ jsxs(
7691
+ "div",
6415
7692
  {
6416
- initial: { opacity: 1 },
6417
- exit: { opacity: 0 },
6418
- transition: { duration: 0.3 },
6419
- className: "payman-v2-chat-layout",
6420
- style: { justifyContent: "center", alignItems: "center" },
6421
- children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", flex: 1, width: "100%" }, children: [
6422
- /* @__PURE__ */ jsx(
6423
- MessageList,
6424
- {
6425
- messages,
6426
- isLoading: false,
6427
- emptyStateText,
6428
- showEmptyStateIcon,
6429
- emptyStateComponent,
6430
- layout,
6431
- showTimestamps,
6432
- stage: config.stage || "DEVELOPMENT",
6433
- animated,
6434
- showAgentName,
6435
- agentName,
6436
- showAvatars,
6437
- showUserAvatar,
6438
- showAssistantAvatar,
6439
- showExecutionSteps,
6440
- showStreamingDot,
6441
- streamingStepsText,
6442
- completedStepsText,
6443
- onExecutionTraceClick,
6444
- onLoadMoreMessages,
6445
- isLoadingMoreMessages,
6446
- hasMoreMessages
6447
- }
6448
- ),
6449
- /* @__PURE__ */ jsx(
7693
+ className: cn(
7694
+ "payman-v2-split-layout",
7695
+ pdfPreviewMode === "sheet" && "payman-v2-split-layout--sheet"
7696
+ ),
7697
+ children: [
7698
+ /* @__PURE__ */ jsx("div", { className: "payman-v2-chat-column", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: isEmpty && !hasEverSentMessage ? /* @__PURE__ */ jsx(
6450
7699
  motion.div,
6451
7700
  {
6452
- initial: { opacity: 0, y: 12 },
6453
- animate: { opacity: 1, y: 0 },
6454
- transition: { delay: 0.2, duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] },
6455
- style: { width: "100%" },
6456
- children: hasAskPermission && /* @__PURE__ */ jsx(
6457
- ChatInputV2,
6458
- {
6459
- ref: chatInputV2Ref,
6460
- onSend: handleV2Send,
6461
- onCancel: cancelStream,
6462
- disabled: isV2InputDisabled,
6463
- isStreaming: isWaitingForResponse,
6464
- placeholder: isRecording ? "Listening..." : placeholder,
6465
- enableVoice: config.enableVoice === true,
6466
- transcribedText: config.enableVoice === true ? transcribedText : "",
6467
- voiceAvailable: config.enableVoice === true && voiceAvailable,
6468
- isRecording,
6469
- onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
6470
- onCancelRecording: handleCancelRecording,
6471
- onConfirmRecording: handleConfirmRecording,
6472
- showResetSession,
6473
- onResetSession: requestResetSession,
6474
- showAttachmentButton,
6475
- showUploadImageButton,
6476
- showAttachFileButton,
6477
- onUploadImageClick,
6478
- onAttachFileClick,
6479
- editingMessageId,
6480
- onClearEditing: handleClearEditing,
6481
- analysisMode: enableDeepModeToggle ? analysisMode : void 0,
6482
- onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
6483
- slashCommands
6484
- }
6485
- )
6486
- }
6487
- )
6488
- ] })
6489
- },
6490
- "v2-empty"
6491
- ) : /* @__PURE__ */ jsxs(
6492
- motion.div,
6493
- {
6494
- initial: hasEverSentMessage ? { opacity: 0 } : false,
6495
- animate: { opacity: 1 },
6496
- transition: { duration: 0.3 },
6497
- className: "payman-v2-chat-layout",
6498
- children: [
6499
- /* @__PURE__ */ jsx(
6500
- MessageListV2,
7701
+ initial: { opacity: 1 },
7702
+ exit: { opacity: 0 },
7703
+ transition: { duration: 0.3 },
7704
+ className: "payman-v2-chat-layout",
7705
+ style: { justifyContent: "center", alignItems: "center" },
7706
+ children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", flex: 1, width: "100%" }, children: [
7707
+ /* @__PURE__ */ jsx(
7708
+ MessageList,
7709
+ {
7710
+ messages,
7711
+ isLoading: false,
7712
+ emptyStateText,
7713
+ showEmptyStateIcon,
7714
+ emptyStateComponent,
7715
+ layout,
7716
+ showTimestamps,
7717
+ stage: config.stage || "DEVELOPMENT",
7718
+ animated,
7719
+ showAgentName,
7720
+ agentName,
7721
+ showAvatars,
7722
+ showUserAvatar,
7723
+ showAssistantAvatar,
7724
+ showExecutionSteps,
7725
+ showStreamingDot,
7726
+ streamingStepsText,
7727
+ completedStepsText,
7728
+ onExecutionTraceClick,
7729
+ onLoadMoreMessages,
7730
+ isLoadingMoreMessages,
7731
+ hasMoreMessages
7732
+ }
7733
+ ),
7734
+ /* @__PURE__ */ jsx(
7735
+ motion.div,
7736
+ {
7737
+ initial: { opacity: 0, y: 12 },
7738
+ animate: { opacity: 1, y: 0 },
7739
+ transition: { delay: 0.2, duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] },
7740
+ style: { width: "100%" },
7741
+ children: hasAskPermission && /* @__PURE__ */ jsx(
7742
+ ChatInputV2,
7743
+ {
7744
+ ref: chatInputV2Ref,
7745
+ onSend: handleV2Send,
7746
+ onCancel: cancelStream,
7747
+ disabled: isV2InputDisabled,
7748
+ isStreaming: isWaitingForResponse,
7749
+ isUploadingAttachments: attachmentUpload.isUploading,
7750
+ attachmentsReady: attachmentUpload.allReady,
7751
+ hasAttachmentUploadErrors: attachmentUpload.hasErrors,
7752
+ attachmentUploadStatusById: attachmentUpload.statusById,
7753
+ uploadedAttachmentPayloads: attachmentUpload.payloads,
7754
+ onAttachmentsChange: handleAttachmentsChange,
7755
+ placeholder: isRecording ? "Listening..." : placeholder,
7756
+ enableVoice: config.enableVoice === true,
7757
+ transcribedText: config.enableVoice === true ? transcribedText : "",
7758
+ voiceAvailable: config.enableVoice === true && voiceAvailable,
7759
+ isRecording,
7760
+ onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
7761
+ onCancelRecording: handleCancelRecording,
7762
+ onConfirmRecording: handleConfirmRecording,
7763
+ showResetSession,
7764
+ onResetSession: requestResetSession,
7765
+ showAttachmentButton: attachmentSettings.showAttachmentButton,
7766
+ showUploadImageButton: attachmentSettings.showUploadImageButton,
7767
+ showAttachFileButton: attachmentSettings.showAttachFileButton,
7768
+ allowedImageExtensions: attachmentSettings.allowedImageExtensions,
7769
+ allowedFileExtensions: attachmentSettings.allowedFileExtensions,
7770
+ maxCount: attachmentSettings.maxCount,
7771
+ maxFileBytes: attachmentSettings.maxFileBytes,
7772
+ maxTotalBytes: attachmentSettings.maxTotalBytes,
7773
+ onUploadImageClick,
7774
+ onAttachFileClick,
7775
+ editingMessageId,
7776
+ onClearEditing: handleClearEditing,
7777
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
7778
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
7779
+ slashCommands
7780
+ }
7781
+ )
7782
+ }
7783
+ )
7784
+ ] })
7785
+ },
7786
+ "v2-empty"
7787
+ ) : /* @__PURE__ */ jsxs(
7788
+ motion.div,
6501
7789
  {
6502
- ref: messageListV2Ref,
6503
- messages,
6504
- isStreaming: isWaitingForResponse,
6505
- onEditUserMessage: handleEditMessageDraft,
6506
- onRetryUserMessage: handleRetryUserMessage,
6507
- onImageClick: handleImageClick,
6508
- onExecutionTraceClick,
6509
- messageActions,
6510
- retryDisabled: isWaitingForResponse,
6511
- typingSpeed: config.typingSpeed ?? 4,
6512
- userActionPrompts,
6513
- notifications,
6514
- onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
6515
- onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
6516
- onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
6517
- onExpireUserAction: isUserActionSupported ? expireUserAction2 : void 0,
6518
- onDismissNotification: dismissNotification,
6519
- onSubmitFeedback: handleSubmitFeedback
6520
- }
6521
- ),
6522
- /* @__PURE__ */ jsx(
6523
- StreamingIndicatorV2,
7790
+ initial: hasEverSentMessage ? { opacity: 0 } : false,
7791
+ animate: { opacity: 1 },
7792
+ transition: { duration: 0.3 },
7793
+ className: "payman-v2-chat-layout",
7794
+ children: [
7795
+ /* @__PURE__ */ jsx(
7796
+ MessageListV2,
7797
+ {
7798
+ ref: messageListV2Ref,
7799
+ messages,
7800
+ isStreaming: isWaitingForResponse,
7801
+ sidePanelOpen: !!pdfSheet,
7802
+ onEditUserMessage: handleEditMessageDraft,
7803
+ onRetryUserMessage: handleRetryUserMessage,
7804
+ onImageClick: handleImageClick,
7805
+ onExecutionTraceClick,
7806
+ messageActions,
7807
+ retryDisabled: isWaitingForResponse,
7808
+ typingSpeed: config.typingSpeed ?? 4,
7809
+ userActionPrompts,
7810
+ notifications,
7811
+ onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
7812
+ onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
7813
+ onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
7814
+ onDismissNotification: dismissNotification,
7815
+ onSubmitFeedback: handleSubmitFeedback
7816
+ }
7817
+ ),
7818
+ /* @__PURE__ */ jsx(
7819
+ StreamingIndicatorV2,
7820
+ {
7821
+ isStreaming: isWaitingForResponse,
7822
+ loadingAnimation: config.loadingAnimation
7823
+ }
7824
+ ),
7825
+ hasAskPermission && /* @__PURE__ */ jsx(
7826
+ ChatInputV2,
7827
+ {
7828
+ ref: chatInputV2Ref,
7829
+ onSend: handleV2Send,
7830
+ onCancel: cancelStream,
7831
+ disabled: isV2InputDisabled,
7832
+ isStreaming: isWaitingForResponse,
7833
+ isUploadingAttachments: attachmentUpload.isUploading,
7834
+ attachmentsReady: attachmentUpload.allReady,
7835
+ hasAttachmentUploadErrors: attachmentUpload.hasErrors,
7836
+ attachmentUploadStatusById: attachmentUpload.statusById,
7837
+ uploadedAttachmentPayloads: attachmentUpload.payloads,
7838
+ onAttachmentsChange: handleAttachmentsChange,
7839
+ placeholder: isRecording ? "Listening..." : placeholder,
7840
+ enableVoice: config.enableVoice === true,
7841
+ transcribedText: config.enableVoice === true ? transcribedText : "",
7842
+ voiceAvailable: config.enableVoice === true && voiceAvailable,
7843
+ isRecording,
7844
+ onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
7845
+ onCancelRecording: handleCancelRecording,
7846
+ onConfirmRecording: handleConfirmRecording,
7847
+ showResetSession,
7848
+ onResetSession: requestResetSession,
7849
+ showAttachmentButton: attachmentSettings.showAttachmentButton,
7850
+ showUploadImageButton: attachmentSettings.showUploadImageButton,
7851
+ showAttachFileButton: attachmentSettings.showAttachFileButton,
7852
+ allowedImageExtensions: attachmentSettings.allowedImageExtensions,
7853
+ allowedFileExtensions: attachmentSettings.allowedFileExtensions,
7854
+ maxCount: attachmentSettings.maxCount,
7855
+ maxFileBytes: attachmentSettings.maxFileBytes,
7856
+ maxTotalBytes: attachmentSettings.maxTotalBytes,
7857
+ onUploadImageClick,
7858
+ onAttachFileClick,
7859
+ editingMessageId,
7860
+ onClearEditing: handleClearEditing,
7861
+ analysisMode: enableDeepModeToggle ? analysisMode : void 0,
7862
+ onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
7863
+ slashCommands
7864
+ }
7865
+ )
7866
+ ]
7867
+ },
7868
+ "v2-chat"
7869
+ ) }) }),
7870
+ pdfPreviewMode === "split" && /* @__PURE__ */ jsx(
7871
+ PdfSheetV2,
6524
7872
  {
6525
- isStreaming: isWaitingForResponse,
6526
- loadingAnimation: config.loadingAnimation
7873
+ src: pdfSheet?.href ?? null,
7874
+ title: pdfSheet?.title ?? "",
7875
+ onClose: closePdfSheet,
7876
+ mode: "split"
6527
7877
  }
6528
7878
  ),
6529
- hasAskPermission && /* @__PURE__ */ jsx(
6530
- ChatInputV2,
7879
+ pdfPreviewMode === "sheet" && /* @__PURE__ */ jsx(
7880
+ "div",
6531
7881
  {
6532
- ref: chatInputV2Ref,
6533
- onSend: handleV2Send,
6534
- onCancel: cancelStream,
6535
- disabled: isV2InputDisabled,
6536
- isStreaming: isWaitingForResponse,
6537
- placeholder: isRecording ? "Listening..." : placeholder,
6538
- enableVoice: config.enableVoice === true,
6539
- transcribedText: config.enableVoice === true ? transcribedText : "",
6540
- voiceAvailable: config.enableVoice === true && voiceAvailable,
6541
- isRecording,
6542
- onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
6543
- onCancelRecording: handleCancelRecording,
6544
- onConfirmRecording: handleConfirmRecording,
6545
- showResetSession,
6546
- onResetSession: requestResetSession,
6547
- showAttachmentButton,
6548
- showUploadImageButton,
6549
- showAttachFileButton,
6550
- onUploadImageClick,
6551
- onAttachFileClick,
6552
- editingMessageId,
6553
- onClearEditing: handleClearEditing,
6554
- analysisMode: enableDeepModeToggle ? analysisMode : void 0,
6555
- onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
6556
- slashCommands
7882
+ className: "payman-v2-pdf-sheet-top-anchor",
7883
+ "aria-hidden": !pdfSheet,
7884
+ children: /* @__PURE__ */ jsx(
7885
+ PdfSheetV2,
7886
+ {
7887
+ src: pdfSheet?.href ?? null,
7888
+ title: pdfSheet?.title ?? "",
7889
+ onClose: closePdfSheet,
7890
+ mode: "sheet"
7891
+ }
7892
+ )
6557
7893
  }
6558
7894
  )
6559
7895
  ]
6560
- },
6561
- "v2-chat"
6562
- ) }),
7896
+ }
7897
+ ),
6563
7898
  /* @__PURE__ */ jsx(
6564
7899
  ImageLightboxV2,
6565
7900
  {
@@ -6594,10 +7929,19 @@ var PaymanChat = forwardRef(
6594
7929
  function PaymanChat2(props, ref) {
6595
7930
  const mergedCallbacks = useSentryChatCallbacks(props.callbacks, props.config);
6596
7931
  const chat = useChatV2(props.config, mergedCallbacks);
6597
- return /* @__PURE__ */ jsx(PaymanChatInner, { ...props, chat, ref });
7932
+ const attachmentUpload = useAttachmentUpload(props.config);
7933
+ return /* @__PURE__ */ jsx(
7934
+ PaymanChatInner,
7935
+ {
7936
+ ...props,
7937
+ chat,
7938
+ attachmentUpload,
7939
+ ref
7940
+ }
7941
+ );
6598
7942
  }
6599
7943
  );
6600
7944
 
6601
- export { PaymanChat, PaymanChatContext, UserActionStaleError, cancelUserAction, captureSentryError, cn, expireUserAction, formatDate, resendUserAction, submitUserAction, useChatV2, usePaymanChat, useVoice };
7945
+ export { PaymanChat, PaymanChatContext, PdfSheetV2, UserActionStaleError, buildSignedUrlEndpoint, cancelUserAction, captureSentryError, cn, formatAttachmentBytes, formatDate, getPdfTitleFromUrl, isPdfUrl, mapExecutionHistoryPageToChatMessages, mapExecutionHistoryToChatMessages, resendUserAction, stripAttachmentsSuffixFromIntent, submitUserAction, uploadAttachment, uploadAttachments, useAttachmentUpload, useChatV2, usePaymanChat, useVoice };
6602
7946
  //# sourceMappingURL=index.mjs.map
6603
7947
  //# sourceMappingURL=index.mjs.map