@paymanai/payman-ask-sdk 4.0.18 → 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/;
@@ -846,17 +854,6 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
846
854
  signal: abortController.signal,
847
855
  onEvent: (event) => {
848
856
  if (abortController.signal.aborted) return;
849
- try {
850
- const et = event?.eventType;
851
- if (et === "RUN_IN_PROGRESS" || et === "INTENT_PROGRESS" || et === "THINKING_DELTA") {
852
- const len = (event.partialText ?? event.text ?? "").length;
853
- console.log(`[stream] ${et} (+${len} chars)`);
854
- } else {
855
- console.log(`[stream] ${et ?? "?"}:`, JSON.stringify(event));
856
- }
857
- } catch {
858
- console.log("[stream] (unserializable event)", event?.eventType);
859
- }
860
857
  processStreamEventV2(event, state);
861
858
  if (state.lastUserAction) {
862
859
  callbacksRef.current.onUserActionRequired?.(state.lastUserAction);
@@ -1052,10 +1049,85 @@ function createCancelledMessageUpdate(steps, currentMessage) {
1052
1049
  currentMessage: currentMessage || "Thinking..."
1053
1050
  };
1054
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
+ }
1055
1127
  var UserActionStaleError = class extends Error {
1056
1128
  constructor(userActionId, message = "User action is no longer actionable") {
1057
1129
  super(message);
1058
- __publicField(this, "userActionId");
1130
+ __publicField2(this, "userActionId");
1059
1131
  this.name = "UserActionStaleError";
1060
1132
  this.userActionId = userActionId;
1061
1133
  }
@@ -1116,12 +1188,32 @@ function getStoredOrInitialMessages(config) {
1116
1188
  function getSessionIdFromMessages(messages) {
1117
1189
  return messages.find((message) => message.sessionId)?.sessionId;
1118
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
+ }
1119
1210
  function useChatV2(config, callbacks = {}) {
1120
1211
  const [messages, setMessages] = useState(() => getStoredOrInitialMessages(config));
1121
1212
  const [isWaitingForResponse, setIsWaitingForResponse] = useState(() => {
1122
1213
  if (!config.userId) return false;
1123
1214
  return activeStreamStore.get(config.userId)?.isWaiting ?? false;
1124
1215
  });
1216
+ const [isUploadingAttachments, setIsUploadingAttachments] = useState(false);
1125
1217
  const sessionIdRef = useRef(
1126
1218
  getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
1127
1219
  );
@@ -1196,21 +1288,45 @@ function useChatV2(config, callbacks = {}) {
1196
1288
  );
1197
1289
  const sendMessage = useCallback(
1198
1290
  async (userMessage, options) => {
1199
- 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
+ }
1200
1309
  if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
1201
1310
  sessionIdRef.current = generateId();
1202
1311
  callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
1203
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;
1204
1319
  const userMessageId = `user-${Date.now()}`;
1205
1320
  const userMsg = {
1206
1321
  id: userMessageId,
1207
1322
  sessionId: sessionIdRef.current,
1208
1323
  role: "user",
1209
- content: userMessage,
1210
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1324
+ content: trimmedMessage,
1325
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1326
+ attachments: messageAttachments
1211
1327
  };
1212
1328
  setMessages((prev) => [...prev, userMsg]);
1213
- callbacksRef.current.onMessageSent?.(userMessage);
1329
+ callbacksRef.current.onMessageSent?.(trimmedMessage);
1214
1330
  setIsWaitingForResponse(true);
1215
1331
  callbacksRef.current.onStreamStart?.();
1216
1332
  const streamingId = `assistant-${Date.now()}`;
@@ -1237,11 +1353,14 @@ function useChatV2(config, callbacks = {}) {
1237
1353
  activeStreamStore.start(userId, abortController, initialMessages);
1238
1354
  }
1239
1355
  const newSessionId = await startStream(
1240
- userMessage,
1356
+ trimmedMessage,
1241
1357
  streamingId,
1242
1358
  sessionIdRef.current,
1243
1359
  abortController,
1244
- options
1360
+ {
1361
+ analysisMode: options?.analysisMode,
1362
+ attachments: streamAttachments
1363
+ }
1245
1364
  );
1246
1365
  const finalStreamUserId = streamUserIdRef.current ?? userId;
1247
1366
  if (finalStreamUserId) {
@@ -1419,6 +1538,18 @@ function useChatV2(config, callbacks = {}) {
1419
1538
  setMessages(config.initialMessages);
1420
1539
  sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
1421
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]);
1422
1553
  useEffect(() => {
1423
1554
  const prevUserId = prevUserIdRef.current;
1424
1555
  prevUserIdRef.current = config.userId;
@@ -1453,6 +1584,7 @@ function useChatV2(config, callbacks = {}) {
1453
1584
  getSessionId,
1454
1585
  getMessages,
1455
1586
  isWaitingForResponse,
1587
+ isUploadingAttachments,
1456
1588
  sessionId: sessionIdRef.current,
1457
1589
  userActionState,
1458
1590
  submitUserAction: submitUserAction2,
@@ -1676,6 +1808,102 @@ function useVoice(config = {}, callbacks = {}) {
1676
1808
  reset
1677
1809
  };
1678
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
+ }
1679
1907
  function classifyField(field) {
1680
1908
  if (!field) return "text";
1681
1909
  if (Array.isArray(field.oneOf) && field.oneOf.length > 0) return "select";
@@ -1792,6 +2020,66 @@ function buildContent(schema, values) {
1792
2020
  }
1793
2021
  return content;
1794
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
+ }
1795
2083
  var PaymanChatContext = createContext(void 0);
1796
2084
  function usePaymanChat() {
1797
2085
  const context = useContext(PaymanChatContext);
@@ -1902,6 +2190,111 @@ function subscribeToCfRay(urlPattern, listener) {
1902
2190
  };
1903
2191
  }
1904
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
+
1905
2298
  // src/utils/slashCommands.ts
1906
2299
  var DEFAULT_SLASH_COMMANDS = [
1907
2300
  {
@@ -3053,6 +3446,108 @@ function ActionTooltipV2({ label, children }) {
3053
3446
  ] }) })
3054
3447
  ] });
3055
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
+ }
3056
3551
  function formatMessageTime(timestamp) {
3057
3552
  const value = new Date(timestamp);
3058
3553
  if (Number.isNaN(value.getTime())) return "";
@@ -3065,6 +3560,7 @@ function UserMessageV2({
3065
3560
  message,
3066
3561
  onEdit,
3067
3562
  onRetry,
3563
+ onImageClick,
3068
3564
  retryDisabled = false,
3069
3565
  actions
3070
3566
  }) {
@@ -3147,7 +3643,15 @@ function UserMessageV2({
3147
3643
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3148
3644
  toastPortal,
3149
3645
  /* @__PURE__ */ jsx("div", { className: "payman-v2-user-msg payman-v2-fade-in", children: /* @__PURE__ */ jsxs("div", { className: "payman-v2-user-msg-group", children: [
3150
- /* @__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: [
3151
3655
  /* @__PURE__ */ jsx("span", { className: "payman-v2-user-msg-command-chip", children: parsedCommand.command }),
3152
3656
  parsedCommand.body.trim() ? /* @__PURE__ */ jsx("p", { className: "payman-v2-user-msg-text", children: parsedCommand.body }) : null
3153
3657
  ] }) : /* @__PURE__ */ jsx("p", { className: "payman-v2-user-msg-text", children: message.content }) }),
@@ -3372,9 +3876,44 @@ function MarkdownImageV2({
3372
3876
  }
3373
3877
  ) });
3374
3878
  }
3375
- 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) {
3376
3909
  return {
3377
- 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
+ },
3378
3917
  code: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
3379
3918
  pre: ({ children }) => /* @__PURE__ */ jsx("div", { className: "payman-v2-markdown-pre", children: /* @__PURE__ */ jsx("pre", { children }) }),
3380
3919
  ul: ({ children }) => /* @__PURE__ */ jsx("ul", { children }),
@@ -3387,7 +3926,15 @@ function buildComponents(onImageClick, isResolvingRef) {
3387
3926
  em: ({ children }) => /* @__PURE__ */ jsx("em", { children }),
3388
3927
  blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", { children }),
3389
3928
  hr: () => /* @__PURE__ */ jsx("hr", {}),
3390
- 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
+ },
3391
3938
  img: ({ src, alt }) => /* @__PURE__ */ jsx(
3392
3939
  MarkdownImageV2,
3393
3940
  {
@@ -3408,13 +3955,15 @@ function MarkdownRendererV2({
3408
3955
  content,
3409
3956
  isStreaming,
3410
3957
  isResolvingImages,
3411
- onImageClick
3958
+ onImageClick,
3959
+ onPdfClick,
3960
+ autoOpenPdf
3412
3961
  }) {
3413
3962
  const isResolvingRef = useRef(isResolvingImages);
3414
3963
  isResolvingRef.current = isResolvingImages;
3415
3964
  const components = useMemo(
3416
- () => buildComponents(onImageClick, isResolvingRef),
3417
- [onImageClick]
3965
+ () => buildComponents(onImageClick, isResolvingRef, onPdfClick, autoOpenPdf),
3966
+ [onImageClick, onPdfClick, autoOpenPdf]
3418
3967
  );
3419
3968
  return /* @__PURE__ */ jsx(
3420
3969
  "div",
@@ -3783,6 +4332,17 @@ function stripIncompleteImageToken(text) {
3783
4332
  if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
3784
4333
  return text.slice(0, lastBang);
3785
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
+ }
3786
4346
  function getFeedbackState(message) {
3787
4347
  const feedback = message.feedback;
3788
4348
  if (feedback === "up" || feedback === "down") return feedback;
@@ -3806,6 +4366,12 @@ function AssistantMessageV2({
3806
4366
  () => getFeedbackState(message)
3807
4367
  );
3808
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
+ }, []);
3809
4375
  const canSubmitFeedback = !!onSubmitFeedback && !!message.executionId;
3810
4376
  const [toast, setToast] = useState(null);
3811
4377
  const copyResetTimerRef = useRef(null);
@@ -3830,7 +4396,7 @@ function AssistantMessageV2({
3830
4396
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
3831
4397
  if (!raw) return "";
3832
4398
  const normalized = raw.replace(/\\n/g, "\n");
3833
- return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
4399
+ return message.isStreaming ? stripIncompleteMarkdownTokens(normalized) : normalized;
3834
4400
  })();
3835
4401
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
3836
4402
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
@@ -4000,10 +4566,12 @@ function AssistantMessageV2({
4000
4566
  /* @__PURE__ */ jsx("div", { className: "payman-v2-assistant-msg-content-area", children: displayContent ? /* @__PURE__ */ jsx(
4001
4567
  MarkdownRendererV2,
4002
4568
  {
4003
- content: displayContent,
4569
+ content: message.isStreaming && !isCancelled || isResponseTyping ? stripIncompleteMarkdownTokens(displayContent) : displayContent,
4004
4570
  isStreaming: message.isStreaming && !isCancelled || isResponseTyping,
4005
4571
  isResolvingImages: message.isResolvingImages,
4006
- onImageClick
4572
+ onImageClick,
4573
+ onPdfClick: handlePdfClick,
4574
+ autoOpenPdf: hasEverStreamed.current
4007
4575
  }
4008
4576
  ) : !isThinkingStreaming ? /* @__PURE__ */ jsx("span", { className: "payman-v2-assistant-msg-placeholder", children: "..." }) : null }),
4009
4577
  isCancelled && message.isStreaming && /* @__PURE__ */ jsxs("div", { className: "payman-v2-assistant-msg-paused", children: [
@@ -4220,7 +4788,6 @@ function VerificationInline({
4220
4788
  const [code, setCode] = useState("");
4221
4789
  const [errored, setErrored] = useState(false);
4222
4790
  const [resendSec, setResendSec] = useState(0);
4223
- const [hiddenAfterResend, setHiddenAfterResend] = useState(false);
4224
4791
  const lastSubmittedRef = useRef(null);
4225
4792
  const resendTimerRef = useRef(void 0);
4226
4793
  const status = prompt.status;
@@ -4234,9 +4801,6 @@ function VerificationInline({
4234
4801
  lastSubmittedRef.current = null;
4235
4802
  }
4236
4803
  }, [prompt.subAction, prompt.userActionId]);
4237
- useEffect(() => {
4238
- setHiddenAfterResend(false);
4239
- }, [prompt.expirySeconds, prompt.message, prompt.subAction, prompt.userActionId]);
4240
4804
  useEffect(() => {
4241
4805
  if (code.length < codeLen) lastSubmittedRef.current = null;
4242
4806
  }, [code, codeLen]);
@@ -4287,13 +4851,11 @@ function VerificationInline({
4287
4851
  if (locked || resendSec > 0) return;
4288
4852
  setErrored(false);
4289
4853
  setCode("");
4290
- setHiddenAfterResend(true);
4291
4854
  lastSubmittedRef.current = null;
4292
4855
  try {
4293
4856
  await onResend(prompt.userActionId);
4294
4857
  startResendCooldown();
4295
4858
  } catch {
4296
- setHiddenAfterResend(false);
4297
4859
  }
4298
4860
  }, [locked, onResend, prompt.userActionId, resendSec, startResendCooldown]);
4299
4861
  const handleCancel = useCallback(() => {
@@ -4301,7 +4863,6 @@ function VerificationInline({
4301
4863
  void onCancel(prompt.userActionId);
4302
4864
  }, [busy, onCancel, prompt.userActionId]);
4303
4865
  const description = prompt.message?.trim() || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
4304
- if (hiddenAfterResend) return null;
4305
4866
  return /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua", role: "group", "aria-label": "Verification required", children: [
4306
4867
  /* @__PURE__ */ jsxs("div", { className: "payman-v2-ua-head", children: [
4307
4868
  /* @__PURE__ */ jsx(ShieldCheck, { className: "payman-v2-ua-icon", size: 15, strokeWidth: 1.75, "aria-hidden": true }),
@@ -4397,9 +4958,6 @@ function SchemaFormInline({
4397
4958
  const busy = status === "submitting";
4398
4959
  const stale = status === "stale";
4399
4960
  const locked = busy || stale || expired;
4400
- const isUserConfirmation = prompt.subAction === "UserConfirmation";
4401
- const messageFormat = prompt.metadata?.["payman/messageFormat"];
4402
- const renderMarkdown = messageFormat === "markdown";
4403
4961
  const setValue = (key, value) => {
4404
4962
  setValues((prev) => ({ ...prev, [key]: value }));
4405
4963
  setErrors((prev) => {
@@ -4425,8 +4983,8 @@ function SchemaFormInline({
4425
4983
  /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-title", children: "Action required" }),
4426
4984
  typeof secondsLeft === "number" && !stale && /* @__PURE__ */ jsx("span", { className: "payman-v2-ua-timer", children: expired ? "Expired" : `${secondsLeft}s` })
4427
4985
  ] }),
4428
- 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 })),
4429
- 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]) => {
4430
4988
  const widget = classifyField(field);
4431
4989
  const label = field.title || key;
4432
4990
  const required = isRequired(schema, key);
@@ -4499,7 +5057,7 @@ function SchemaFormInline({
4499
5057
  className: "payman-v2-ua-btn payman-v2-ua-btn-primary",
4500
5058
  disabled: locked,
4501
5059
  onClick: handleSubmit,
4502
- children: busy ? "Submitting\u2026" : isUserConfirmation ? "Confirm" : "Submit"
5060
+ children: busy ? "Submitting\u2026" : "Submit"
4503
5061
  }
4504
5062
  ),
4505
5063
  /* @__PURE__ */ jsx(
@@ -4555,25 +5113,10 @@ function useExpiryCountdown(prompt) {
4555
5113
  const expired = initial !== void 0 && secondsLeft === 0;
4556
5114
  return [secondsLeft, expired];
4557
5115
  }
4558
- function UserActionInline({
4559
- prompt,
4560
- onSubmit,
4561
- onCancel,
4562
- onResend,
4563
- onExpired
4564
- }) {
5116
+ function UserActionInline({ prompt, onSubmit, onCancel, onResend }) {
4565
5117
  const [secondsLeft, expired] = useExpiryCountdown(prompt);
4566
- useEffect(() => {
4567
- if (expired && prompt.kind !== "notification") onExpired?.();
4568
- }, [expired, onExpired, prompt.kind]);
4569
5118
  let body;
4570
- if (expired && prompt.kind !== "notification") {
4571
- const note = {
4572
- id: `${prompt.userActionId}-expired`,
4573
- message: prompt.kind === "verification" ? "Verification Request Expired" : "User Form Expired"
4574
- };
4575
- body = /* @__PURE__ */ jsx(NotificationInline, { notification: note });
4576
- } else if (prompt.kind === "verification") {
5119
+ if (prompt.kind === "verification") {
4577
5120
  body = /* @__PURE__ */ jsx(
4578
5121
  VerificationInline,
4579
5122
  {
@@ -4613,23 +5156,10 @@ function UserActionInline({
4613
5156
  }
4614
5157
  var SCROLL_THRESHOLD2 = 100;
4615
5158
  var USER_SCROLL_UP_EPSILON = 4;
4616
- var PROMPT_KEY_SEPARATOR = "";
4617
- function getPromptSlotKey(prompt) {
4618
- return prompt.toolCallId || prompt.userActionId;
4619
- }
4620
- function getPromptViewKey(prompt) {
4621
- return [
4622
- getPromptSlotKey(prompt),
4623
- prompt.userActionId,
4624
- prompt.subAction ?? "",
4625
- prompt.expirySeconds ?? "",
4626
- prompt.message ?? ""
4627
- ].join(PROMPT_KEY_SEPARATOR);
4628
- }
4629
5159
  var MessageListV2 = forwardRef(
4630
5160
  function MessageListV22({
4631
5161
  messages,
4632
- isStreaming = false,
5162
+ isStreaming: _isStreaming = false,
4633
5163
  onEditUserMessage,
4634
5164
  onRetryUserMessage,
4635
5165
  onImageClick,
@@ -4643,7 +5173,8 @@ var MessageListV2 = forwardRef(
4643
5173
  onResendUserAction,
4644
5174
  onDismissNotification,
4645
5175
  onSubmitFeedback,
4646
- typingSpeed = 4
5176
+ typingSpeed = 4,
5177
+ sidePanelOpen = false
4647
5178
  }, ref) {
4648
5179
  const noop = useCallback(async () => {
4649
5180
  }, []);
@@ -4651,10 +5182,11 @@ var MessageListV2 = forwardRef(
4651
5182
  const scrollInnerRef = useRef(null);
4652
5183
  const isNearBottomRef = useRef(true);
4653
5184
  const [showScrollBtn, setShowScrollBtn] = useState(false);
4654
- const [expiredPromptViewState, setExpiredPromptViewState] = useState({});
4655
5185
  const prevCountRef = useRef(messages.length);
4656
5186
  const pauseStickUntilUserMessageRef = useRef(false);
4657
5187
  const followingBottomRef = useRef(true);
5188
+ const sidePanelOpenRef = useRef(sidePanelOpen);
5189
+ sidePanelOpenRef.current = sidePanelOpen;
4658
5190
  const lastPinAtRef = useRef(0);
4659
5191
  const prevScrollTopRef = useRef(0);
4660
5192
  const getDistanceFromBottom = useCallback(() => {
@@ -4662,81 +5194,6 @@ var MessageListV2 = forwardRef(
4662
5194
  if (!el) return 0;
4663
5195
  return el.scrollHeight - el.scrollTop - el.clientHeight;
4664
5196
  }, []);
4665
- const messageActivityFingerprint = useMemo(() => {
4666
- const last = messages[messages.length - 1];
4667
- const promptFingerprint = (userActionPrompts ?? []).map((prompt) => `${getPromptViewKey(prompt)}:${prompt.status}`).join(PROMPT_KEY_SEPARATOR);
4668
- const notificationFingerprint = (notifications ?? []).map((notification) => notification.id).join(PROMPT_KEY_SEPARATOR);
4669
- if (!last) {
4670
- return [
4671
- 0,
4672
- isStreaming ? "streaming" : "idle",
4673
- promptFingerprint,
4674
- notificationFingerprint
4675
- ].join(PROMPT_KEY_SEPARATOR);
4676
- }
4677
- return [
4678
- messages.length,
4679
- isStreaming ? "streaming" : "idle",
4680
- last.id,
4681
- last.role,
4682
- last.content ?? "",
4683
- last.isStreaming ? "streaming" : "done",
4684
- last.streamProgress ?? "",
4685
- last.steps?.length ?? 0,
4686
- last.errorDetails ?? "",
4687
- promptFingerprint,
4688
- notificationFingerprint
4689
- ].join(PROMPT_KEY_SEPARATOR);
4690
- }, [isStreaming, messages, notifications, userActionPrompts]);
4691
- const handleUserActionExpired = useCallback(
4692
- (promptKey) => {
4693
- setExpiredPromptViewState((prev) => {
4694
- if (prev[promptKey]) return prev;
4695
- return {
4696
- ...prev,
4697
- [promptKey]: { baseline: messageActivityFingerprint, hidden: false }
4698
- };
4699
- });
4700
- },
4701
- [messageActivityFingerprint]
4702
- );
4703
- useEffect(() => {
4704
- setExpiredPromptViewState((prev) => {
4705
- let changed = false;
4706
- const next = {};
4707
- for (const [key, state] of Object.entries(prev)) {
4708
- if (!state.hidden && state.baseline !== messageActivityFingerprint) {
4709
- next[key] = { ...state, hidden: true };
4710
- changed = true;
4711
- } else {
4712
- next[key] = state;
4713
- }
4714
- }
4715
- return changed ? next : prev;
4716
- });
4717
- }, [messageActivityFingerprint]);
4718
- useEffect(() => {
4719
- const livePromptKeys = new Set((userActionPrompts ?? []).map(getPromptViewKey));
4720
- setExpiredPromptViewState((prev) => {
4721
- let changed = false;
4722
- const next = {};
4723
- for (const [key, state] of Object.entries(prev)) {
4724
- if (livePromptKeys.has(key)) {
4725
- next[key] = state;
4726
- } else {
4727
- changed = true;
4728
- }
4729
- }
4730
- return changed ? next : prev;
4731
- });
4732
- }, [userActionPrompts]);
4733
- const visibleUserActionPrompts = useMemo(
4734
- () => userActionPrompts?.filter((prompt) => {
4735
- const promptKey = getPromptViewKey(prompt);
4736
- return !expiredPromptViewState[promptKey]?.hidden;
4737
- }),
4738
- [expiredPromptViewState, userActionPrompts]
4739
- );
4740
5197
  const pinToBottom = useCallback((behavior = "instant") => {
4741
5198
  const el = scrollRef.current;
4742
5199
  if (!el) return;
@@ -4777,17 +5234,25 @@ var MessageListV2 = forwardRef(
4777
5234
  const nearBottom = distance <= SCROLL_THRESHOLD2;
4778
5235
  isNearBottomRef.current = nearBottom;
4779
5236
  setShowScrollBtn(!nearBottom);
4780
- const sincePin = performance.now() - lastPinAtRef.current;
4781
- if (sincePin < 250) return;
4782
5237
  const scrolledUp = currentScrollTop < prevScrollTop - USER_SCROLL_UP_EPSILON;
4783
5238
  if (scrolledUp) {
4784
5239
  followingBottomRef.current = false;
4785
5240
  pauseStickUntilUserMessageRef.current = true;
4786
- } else if (nearBottom) {
5241
+ return;
5242
+ }
5243
+ const sincePin = performance.now() - lastPinAtRef.current;
5244
+ if (sincePin < 250) return;
5245
+ if (nearBottom) {
4787
5246
  followingBottomRef.current = true;
4788
5247
  pauseStickUntilUserMessageRef.current = false;
4789
5248
  }
4790
5249
  }, [getDistanceFromBottom]);
5250
+ const handleWheel = useCallback((e) => {
5251
+ if (e.deltaY < 0) {
5252
+ followingBottomRef.current = false;
5253
+ pauseStickUntilUserMessageRef.current = true;
5254
+ }
5255
+ }, []);
4791
5256
  useEffect(() => {
4792
5257
  const prevCount = prevCountRef.current;
4793
5258
  prevCountRef.current = messages.length;
@@ -4797,7 +5262,7 @@ var MessageListV2 = forwardRef(
4797
5262
  pauseStickUntilUserMessageRef.current = false;
4798
5263
  followingBottomRef.current = true;
4799
5264
  requestAnimationFrame(() => scrollToBottom());
4800
- } else if (!pauseStickUntilUserMessageRef.current && followingBottomRef.current) {
5265
+ } else if (!sidePanelOpenRef.current && !pauseStickUntilUserMessageRef.current && followingBottomRef.current) {
4801
5266
  requestAnimationFrame(() => scrollToBottom("instant"));
4802
5267
  }
4803
5268
  }
@@ -4806,27 +5271,16 @@ var MessageListV2 = forwardRef(
4806
5271
  const inner = scrollInnerRef.current;
4807
5272
  if (!inner) return;
4808
5273
  const pinIfFollowing = () => {
5274
+ if (sidePanelOpenRef.current) return;
4809
5275
  if (pauseStickUntilUserMessageRef.current) return;
4810
5276
  if (!followingBottomRef.current) return;
5277
+ if (getDistanceFromBottom() > SCROLL_THRESHOLD2) return;
4811
5278
  pinToBottom("instant");
4812
5279
  };
4813
- const ro = new ResizeObserver(() => {
4814
- pinIfFollowing();
4815
- });
5280
+ const ro = new ResizeObserver(pinIfFollowing);
4816
5281
  ro.observe(inner);
4817
- const mo = new MutationObserver(() => {
4818
- pinIfFollowing();
4819
- });
4820
- mo.observe(inner, {
4821
- childList: true,
4822
- subtree: true,
4823
- characterData: true
4824
- });
4825
- return () => {
4826
- ro.disconnect();
4827
- mo.disconnect();
4828
- };
4829
- }, [pinToBottom]);
5282
+ return () => ro.disconnect();
5283
+ }, [pinToBottom, getDistanceFromBottom]);
4830
5284
  useEffect(() => {
4831
5285
  if (messages.length > 0) {
4832
5286
  setTimeout(() => scrollToBottom("instant"), 50);
@@ -4838,6 +5292,7 @@ var MessageListV2 = forwardRef(
4838
5292
  {
4839
5293
  ref: scrollRef,
4840
5294
  onScroll: handleScroll,
5295
+ onWheel: handleWheel,
4841
5296
  className: "payman-v2-message-scroll payman-v2-scrollbar",
4842
5297
  children: /* @__PURE__ */ jsxs(
4843
5298
  "div",
@@ -4851,6 +5306,7 @@ var MessageListV2 = forwardRef(
4851
5306
  message,
4852
5307
  onEdit: onEditUserMessage,
4853
5308
  onRetry: onRetryUserMessage,
5309
+ onImageClick,
4854
5310
  retryDisabled,
4855
5311
  actions: messageActions?.userMessageActions
4856
5312
  }
@@ -4873,20 +5329,16 @@ var MessageListV2 = forwardRef(
4873
5329
  },
4874
5330
  note.id
4875
5331
  )),
4876
- visibleUserActionPrompts?.map((prompt) => {
4877
- const promptKey = getPromptViewKey(prompt);
4878
- return /* @__PURE__ */ jsx(
4879
- UserActionInline,
4880
- {
4881
- prompt,
4882
- onSubmit: onSubmitUserAction ?? noop,
4883
- onCancel: onCancelUserAction ?? noop,
4884
- onResend: onResendUserAction ?? noop,
4885
- onExpired: () => handleUserActionExpired(promptKey)
4886
- },
4887
- promptKey
4888
- );
4889
- })
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
+ ))
4890
5342
  ]
4891
5343
  }
4892
5344
  )
@@ -4916,45 +5368,281 @@ var MessageListV2 = forwardRef(
4916
5368
  ] });
4917
5369
  }
4918
5370
  );
4919
- var ChatInputV2 = forwardRef(
4920
- function ChatInputV22({
4921
- onSend,
4922
- disabled = false,
4923
- isStreaming = false,
4924
- placeholder = "Reply...",
4925
- enableVoice = false,
4926
- voiceAvailable = false,
4927
- isRecording = false,
4928
- onVoicePress,
4929
- transcribedText = "",
4930
- showResetSession = false,
4931
- onResetSession,
4932
- showAttachmentButton = true,
4933
- showUploadImageButton = true,
4934
- showAttachFileButton = true,
4935
- onUploadImageClick,
4936
- onAttachFileClick,
4937
- editingMessageId = null,
4938
- onClearEditing,
4939
- analysisMode,
4940
- onAnalysisModeChange,
4941
- slashCommands = []
4942
- }, ref) {
4943
- const [value, setValue] = useState("");
4944
- const [isFocused, setIsFocused] = useState(false);
4945
- const [showActions, setShowActions] = useState(false);
4946
- const [showVoiceTooltip, setShowVoiceTooltip] = useState(false);
4947
- const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
4948
- const [inlineHint, setInlineHint] = useState(null);
4949
- const [commandMenuDismissed, setCommandMenuDismissed] = useState(false);
4950
- const textareaRef = useRef(null);
4951
- const actionsRef = useRef(null);
4952
- const preRecordTextRef = useRef("");
4953
- const voiceTooltipTimerRef = useRef(
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 = []
5511
+ }, ref) {
5512
+ const [value, setValue] = useState("");
5513
+ const [isFocused, setIsFocused] = useState(false);
5514
+ const [showActions, setShowActions] = useState(false);
5515
+ const [showVoiceTooltip, setShowVoiceTooltip] = useState(false);
5516
+ const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
5517
+ const [inlineHint, setInlineHint] = useState(null);
5518
+ const [commandMenuDismissed, setCommandMenuDismissed] = useState(false);
5519
+ const [attachedFiles, setAttachedFiles] = useState([]);
5520
+ const [previewFile, setPreviewFile] = useState(null);
5521
+ const [isSending, setIsSending] = useState(false);
5522
+ const textareaRef = useRef(null);
5523
+ const actionsRef = useRef(null);
5524
+ const imageInputRef = useRef(null);
5525
+ const fileInputRef = useRef(null);
5526
+ const preRecordTextRef = useRef("");
5527
+ const voiceTooltipTimerRef = useRef(
4954
5528
  null
4955
5529
  );
4956
5530
  const voiceDraftSyncActiveRef = useRef(false);
4957
- 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;
4958
5646
  const hasAttachmentOptions = showUploadImageButton || showAttachFileButton;
4959
5647
  const showAttachmentMenuButton = showAttachmentButton && hasAttachmentOptions;
4960
5648
  const showVoiceButton = enableVoice && onVoicePress != null;
@@ -4994,9 +5682,10 @@ var ChatInputV2 = forwardRef(
4994
5682
  const end = message.length;
4995
5683
  textarea.setSelectionRange(end, end);
4996
5684
  });
4997
- }
5685
+ },
5686
+ clearAttachments: clearAttachmentsFromInput
4998
5687
  }),
4999
- [disabled]
5688
+ [disabled, clearAttachmentsFromInput]
5000
5689
  );
5001
5690
  useEffect(() => {
5002
5691
  if (!showActions) return;
@@ -5030,8 +5719,23 @@ var ChatInputV2 = forwardRef(
5030
5719
  const separator = base && !base.endsWith(" ") && transcribedText ? " " : "";
5031
5720
  setValue(`${base}${separator}${transcribedText}`);
5032
5721
  }, [isRecording, transcribedText]);
5033
- const handleSend = useCallback(() => {
5034
- 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
+ }
5035
5739
  const commandHint = getSlashCommandValidationHint(value);
5036
5740
  if (commandHint) {
5037
5741
  setInlineHint(commandHint);
@@ -5041,15 +5745,38 @@ var ChatInputV2 = forwardRef(
5041
5745
  preRecordTextRef.current = "";
5042
5746
  setInlineHint(null);
5043
5747
  onClearEditing?.();
5044
- onSend(value.trim());
5748
+ const textToSend = value.trim();
5749
+ const filesToSend = attachedFiles.map((f) => f.file);
5750
+ const attachmentsToSend = [...uploadedAttachmentPayloads];
5045
5751
  setValue("");
5752
+ clearAttachmentsFromInput();
5046
5753
  requestAnimationFrame(() => {
5047
5754
  if (textareaRef.current) {
5048
5755
  textareaRef.current.style.height = "auto";
5049
5756
  textareaRef.current.focus();
5050
5757
  }
5051
5758
  });
5052
- }, [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
+ ]);
5053
5780
  const selectCommand = useCallback(
5054
5781
  (command) => {
5055
5782
  const insertText = command.insertText ?? `${command.name} `;
@@ -5094,18 +5821,30 @@ var ChatInputV2 = forwardRef(
5094
5821
  }
5095
5822
  if (e.key === "Enter" && !e.shiftKey) {
5096
5823
  e.preventDefault();
5097
- if (isStreaming) return;
5098
- handleSend();
5824
+ if (isStreaming || isSending || isUploadingAttachments) return;
5825
+ void handleSend();
5099
5826
  }
5100
5827
  };
5101
5828
  const handleUploadImageClick = () => {
5829
+ imageInputRef.current?.click();
5102
5830
  onUploadImageClick?.();
5103
5831
  setShowActions(false);
5104
5832
  };
5105
5833
  const handleAttachFileClick = () => {
5834
+ fileInputRef.current?.click();
5106
5835
  onAttachFileClick?.();
5107
5836
  setShowActions(false);
5108
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
+ };
5109
5848
  const hideVoiceTooltip = useCallback(() => {
5110
5849
  if (voiceTooltipTimerRef.current) {
5111
5850
  clearTimeout(voiceTooltipTimerRef.current);
@@ -5133,9 +5872,39 @@ var ChatInputV2 = forwardRef(
5133
5872
  }
5134
5873
  onVoicePress();
5135
5874
  };
5136
- const canSend = value.trim().length > 0 && !disabled;
5137
- const sendDisabled = !canSend || isStreaming;
5875
+ const canSend = (value.trim().length > 0 || attachedFiles.length > 0) && !disabled;
5876
+ const sendDisabled = !canSend || isStreaming || isUploadingAttachments || !attachmentsReady || hasAttachmentUploadErrors || isSending;
5138
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
+ ),
5139
5908
  /* @__PURE__ */ jsx(AnimatePresence, { children: showCommandSuggestions && /* @__PURE__ */ jsx(
5140
5909
  motion.div,
5141
5910
  {
@@ -5174,6 +5943,115 @@ var ChatInputV2 = forwardRef(
5174
5943
  ),
5175
5944
  children: [
5176
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
+ ) }),
5177
6055
  /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: editingMessageId && /* @__PURE__ */ jsxs(
5178
6056
  motion.div,
5179
6057
  {
@@ -5405,13 +6283,13 @@ var ChatInputV2 = forwardRef(
5405
6283
  "button",
5406
6284
  {
5407
6285
  type: "button",
5408
- onClick: handleSend,
6286
+ onClick: () => void handleSend(),
5409
6287
  disabled: sendDisabled,
5410
6288
  className: cn(
5411
6289
  "payman-v2-input-send-btn",
5412
6290
  sendDisabled && "payman-v2-input-send-btn-disabled"
5413
6291
  ),
5414
- "aria-label": "Send message",
6292
+ "aria-label": isUploadingAttachments || isSending ? "Uploading attachments" : "Send message",
5415
6293
  children: /* @__PURE__ */ jsx(
5416
6294
  ArrowUp,
5417
6295
  {
@@ -5946,6 +6824,389 @@ function TimelineBars({
5946
6824
  )
5947
6825
  ] });
5948
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
+ }
5949
7210
  var DEFAULT_USER_ACTION_STATE = {
5950
7211
  prompts: [],
5951
7212
  notifications: []
@@ -6040,7 +7301,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6040
7301
  onLoadMoreMessages,
6041
7302
  isLoadingMoreMessages = false,
6042
7303
  hasMoreMessages = false,
6043
- chat
7304
+ chat,
7305
+ attachmentUpload
6044
7306
  }, ref) {
6045
7307
  const [inputValue, setInputValue] = useState("");
6046
7308
  const prevInputValueRef = useRef(inputValue);
@@ -6119,6 +7381,19 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6119
7381
  }
6120
7382
  }
6121
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
+ }, []);
6122
7397
  const contextValue = useMemo(
6123
7398
  () => ({
6124
7399
  resetSession,
@@ -6127,7 +7402,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6127
7402
  cancelStream,
6128
7403
  getSessionId,
6129
7404
  getMessages,
6130
- isWaitingForResponse
7405
+ isWaitingForResponse,
7406
+ openPdfSheet
6131
7407
  }),
6132
7408
  [
6133
7409
  resetSession,
@@ -6136,7 +7412,8 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6136
7412
  cancelStream,
6137
7413
  getSessionId,
6138
7414
  getMessages,
6139
- isWaitingForResponse
7415
+ isWaitingForResponse,
7416
+ openPdfSheet
6140
7417
  ]
6141
7418
  );
6142
7419
  const {
@@ -6192,7 +7469,10 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6192
7469
  setInputValue("");
6193
7470
  setLightboxSrc(null);
6194
7471
  setLightboxAlt("");
7472
+ setPdfSheet(null);
7473
+ autoOpenedPdfHrefsRef.current.clear();
6195
7474
  chatInputV2Ref.current?.setDraft("");
7475
+ chatInputV2Ref.current?.clearAttachments();
6196
7476
  clearTranscript();
6197
7477
  if (isRecording) {
6198
7478
  stopRecording();
@@ -6243,14 +7523,20 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6243
7523
  emptyStateComponent,
6244
7524
  showResetSession = false,
6245
7525
  enableDeepModeToggle = true,
6246
- showAttachmentButton = true,
6247
- showUploadImageButton = true,
6248
- showAttachFileButton = true,
6249
7526
  messageActions: messageActionsConfig,
6250
7527
  enableSlashCommands = true,
6251
7528
  slashCommands: slashCommandsConfig,
6252
7529
  commandPermissions
6253
7530
  } = config;
7531
+ const attachmentSettings = useMemo(
7532
+ () => resolveChatAttachmentConfig(config),
7533
+ [
7534
+ config.attachments,
7535
+ config.showAttachmentButton,
7536
+ config.showUploadImageButton,
7537
+ config.showAttachFileButton
7538
+ ]
7539
+ );
6254
7540
  const messageActions = useMemo(
6255
7541
  () => ({
6256
7542
  userMessageActions: {
@@ -6338,11 +7624,22 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6338
7624
  };
6339
7625
  const userActionPrompts = isUserActionSupported ? userActionState.prompts : void 0;
6340
7626
  const notifications = userActionState.notifications;
6341
- const handleV2Send = (text) => {
7627
+ const handleAttachmentsChange = useCallback(
7628
+ (attachments) => {
7629
+ attachmentUpload.syncAttachments(attachments);
7630
+ },
7631
+ [attachmentUpload]
7632
+ );
7633
+ const handleV2Send = (text, files = [], attachments = []) => {
6342
7634
  if (isRecording) stopRecording();
6343
- if (text.trim() && !disableInput && isSessionParamsConfigured) {
7635
+ if ((text.trim() || files.length > 0) && !disableInput && isSessionParamsConfigured) {
7636
+ if (files.length > 0 && attachments.length === 0) return;
6344
7637
  setEditingMessageId(null);
6345
- void sendMessage(text.trim(), { analysisMode: effectiveAnalysisMode });
7638
+ void sendMessage(text.trim(), {
7639
+ analysisMode: effectiveAnalysisMode,
7640
+ attachments,
7641
+ files
7642
+ });
6346
7643
  }
6347
7644
  };
6348
7645
  const handleVoicePress = useCallback(async () => {
@@ -6390,155 +7687,214 @@ var PaymanChatInner = forwardRef(function PaymanChatInner2({
6390
7687
  style,
6391
7688
  children: [
6392
7689
  children,
6393
- /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: isEmpty && !hasEverSentMessage ? /* @__PURE__ */ jsx(
6394
- motion.div,
7690
+ /* @__PURE__ */ jsxs(
7691
+ "div",
6395
7692
  {
6396
- initial: { opacity: 1 },
6397
- exit: { opacity: 0 },
6398
- transition: { duration: 0.3 },
6399
- className: "payman-v2-chat-layout",
6400
- style: { justifyContent: "center", alignItems: "center" },
6401
- children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", flex: 1, width: "100%" }, children: [
6402
- /* @__PURE__ */ jsx(
6403
- MessageList,
6404
- {
6405
- messages,
6406
- isLoading: false,
6407
- emptyStateText,
6408
- showEmptyStateIcon,
6409
- emptyStateComponent,
6410
- layout,
6411
- showTimestamps,
6412
- stage: config.stage || "DEVELOPMENT",
6413
- animated,
6414
- showAgentName,
6415
- agentName,
6416
- showAvatars,
6417
- showUserAvatar,
6418
- showAssistantAvatar,
6419
- showExecutionSteps,
6420
- showStreamingDot,
6421
- streamingStepsText,
6422
- completedStepsText,
6423
- onExecutionTraceClick,
6424
- onLoadMoreMessages,
6425
- isLoadingMoreMessages,
6426
- hasMoreMessages
6427
- }
6428
- ),
6429
- /* @__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(
6430
7699
  motion.div,
6431
7700
  {
6432
- initial: { opacity: 0, y: 12 },
6433
- animate: { opacity: 1, y: 0 },
6434
- transition: { delay: 0.2, duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] },
6435
- style: { width: "100%" },
6436
- children: hasAskPermission && /* @__PURE__ */ jsx(
6437
- ChatInputV2,
6438
- {
6439
- ref: chatInputV2Ref,
6440
- onSend: handleV2Send,
6441
- onCancel: cancelStream,
6442
- disabled: isV2InputDisabled,
6443
- isStreaming: isWaitingForResponse,
6444
- placeholder: isRecording ? "Listening..." : placeholder,
6445
- enableVoice: config.enableVoice === true,
6446
- transcribedText: config.enableVoice === true ? transcribedText : "",
6447
- voiceAvailable: config.enableVoice === true && voiceAvailable,
6448
- isRecording,
6449
- onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
6450
- onCancelRecording: handleCancelRecording,
6451
- onConfirmRecording: handleConfirmRecording,
6452
- showResetSession,
6453
- onResetSession: requestResetSession,
6454
- showAttachmentButton,
6455
- showUploadImageButton,
6456
- showAttachFileButton,
6457
- onUploadImageClick,
6458
- onAttachFileClick,
6459
- editingMessageId,
6460
- onClearEditing: handleClearEditing,
6461
- analysisMode: enableDeepModeToggle ? analysisMode : void 0,
6462
- onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
6463
- slashCommands
6464
- }
6465
- )
6466
- }
6467
- )
6468
- ] })
6469
- },
6470
- "v2-empty"
6471
- ) : /* @__PURE__ */ jsxs(
6472
- motion.div,
6473
- {
6474
- initial: hasEverSentMessage ? { opacity: 0 } : false,
6475
- animate: { opacity: 1 },
6476
- transition: { duration: 0.3 },
6477
- className: "payman-v2-chat-layout",
6478
- children: [
6479
- /* @__PURE__ */ jsx(
6480
- 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,
6481
7789
  {
6482
- ref: messageListV2Ref,
6483
- messages,
6484
- isStreaming: isWaitingForResponse,
6485
- onEditUserMessage: handleEditMessageDraft,
6486
- onRetryUserMessage: handleRetryUserMessage,
6487
- onImageClick: handleImageClick,
6488
- onExecutionTraceClick,
6489
- messageActions,
6490
- retryDisabled: isWaitingForResponse,
6491
- typingSpeed: config.typingSpeed ?? 4,
6492
- userActionPrompts,
6493
- notifications,
6494
- onSubmitUserAction: isUserActionSupported ? submitUserAction2 : void 0,
6495
- onCancelUserAction: isUserActionSupported ? cancelUserAction2 : void 0,
6496
- onResendUserAction: isUserActionSupported ? resendUserAction2 : void 0,
6497
- onDismissNotification: dismissNotification,
6498
- onSubmitFeedback: handleSubmitFeedback
6499
- }
6500
- ),
6501
- /* @__PURE__ */ jsx(
6502
- 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,
6503
7872
  {
6504
- isStreaming: isWaitingForResponse,
6505
- loadingAnimation: config.loadingAnimation
7873
+ src: pdfSheet?.href ?? null,
7874
+ title: pdfSheet?.title ?? "",
7875
+ onClose: closePdfSheet,
7876
+ mode: "split"
6506
7877
  }
6507
7878
  ),
6508
- hasAskPermission && /* @__PURE__ */ jsx(
6509
- ChatInputV2,
7879
+ pdfPreviewMode === "sheet" && /* @__PURE__ */ jsx(
7880
+ "div",
6510
7881
  {
6511
- ref: chatInputV2Ref,
6512
- onSend: handleV2Send,
6513
- onCancel: cancelStream,
6514
- disabled: isV2InputDisabled,
6515
- isStreaming: isWaitingForResponse,
6516
- placeholder: isRecording ? "Listening..." : placeholder,
6517
- enableVoice: config.enableVoice === true,
6518
- transcribedText: config.enableVoice === true ? transcribedText : "",
6519
- voiceAvailable: config.enableVoice === true && voiceAvailable,
6520
- isRecording,
6521
- onVoicePress: config.enableVoice === true ? handleVoicePress : void 0,
6522
- onCancelRecording: handleCancelRecording,
6523
- onConfirmRecording: handleConfirmRecording,
6524
- showResetSession,
6525
- onResetSession: requestResetSession,
6526
- showAttachmentButton,
6527
- showUploadImageButton,
6528
- showAttachFileButton,
6529
- onUploadImageClick,
6530
- onAttachFileClick,
6531
- editingMessageId,
6532
- onClearEditing: handleClearEditing,
6533
- analysisMode: enableDeepModeToggle ? analysisMode : void 0,
6534
- onAnalysisModeChange: enableDeepModeToggle ? setAnalysisMode : void 0,
6535
- 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
+ )
6536
7893
  }
6537
7894
  )
6538
7895
  ]
6539
- },
6540
- "v2-chat"
6541
- ) }),
7896
+ }
7897
+ ),
6542
7898
  /* @__PURE__ */ jsx(
6543
7899
  ImageLightboxV2,
6544
7900
  {
@@ -6573,10 +7929,19 @@ var PaymanChat = forwardRef(
6573
7929
  function PaymanChat2(props, ref) {
6574
7930
  const mergedCallbacks = useSentryChatCallbacks(props.callbacks, props.config);
6575
7931
  const chat = useChatV2(props.config, mergedCallbacks);
6576
- 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
+ );
6577
7942
  }
6578
7943
  );
6579
7944
 
6580
- export { PaymanChat, PaymanChatContext, UserActionStaleError, cancelUserAction, captureSentryError, cn, 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 };
6581
7946
  //# sourceMappingURL=index.mjs.map
6582
7947
  //# sourceMappingURL=index.mjs.map