@paymanai/payman-ask-sdk 4.0.24 → 4.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +111 -175
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +111 -175
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +129 -188
- package/dist/index.native.js.map +1 -1
- package/package.json +3 -2
package/dist/index.native.js
CHANGED
|
@@ -55,6 +55,7 @@ var chatStore = {
|
|
|
55
55
|
memoryStore.delete(key);
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
|
+
var EMPTY_USER_ACTION_STATE = { prompts: [], notifications: [] };
|
|
58
59
|
var streams = /* @__PURE__ */ new Map();
|
|
59
60
|
var activeStreamStore = {
|
|
60
61
|
has(key) {
|
|
@@ -63,7 +64,11 @@ var activeStreamStore = {
|
|
|
63
64
|
get(key) {
|
|
64
65
|
const entry = streams.get(key);
|
|
65
66
|
if (!entry) return null;
|
|
66
|
-
return {
|
|
67
|
+
return {
|
|
68
|
+
messages: entry.messages,
|
|
69
|
+
isWaiting: entry.isWaiting,
|
|
70
|
+
userActionState: entry.userActionState
|
|
71
|
+
};
|
|
67
72
|
},
|
|
68
73
|
// Called before startStream — registers the controller and initial messages
|
|
69
74
|
start(key, abortController, initialMessages) {
|
|
@@ -71,6 +76,7 @@ var activeStreamStore = {
|
|
|
71
76
|
streams.set(key, {
|
|
72
77
|
messages: initialMessages,
|
|
73
78
|
isWaiting: true,
|
|
79
|
+
userActionState: EMPTY_USER_ACTION_STATE,
|
|
74
80
|
abortController,
|
|
75
81
|
listeners: existing?.listeners ?? /* @__PURE__ */ new Set()
|
|
76
82
|
});
|
|
@@ -81,20 +87,27 @@ var activeStreamStore = {
|
|
|
81
87
|
if (!entry) return;
|
|
82
88
|
const next = typeof updater === "function" ? updater(entry.messages) : updater;
|
|
83
89
|
entry.messages = next;
|
|
84
|
-
entry.listeners.forEach((l) => l(next, entry.isWaiting));
|
|
90
|
+
entry.listeners.forEach((l) => l(next, entry.isWaiting, entry.userActionState));
|
|
85
91
|
},
|
|
86
92
|
setWaiting(key, waiting) {
|
|
87
93
|
const entry = streams.get(key);
|
|
88
94
|
if (!entry) return;
|
|
89
95
|
entry.isWaiting = waiting;
|
|
90
|
-
entry.listeners.forEach((l) => l(entry.messages, waiting));
|
|
96
|
+
entry.listeners.forEach((l) => l(entry.messages, waiting, entry.userActionState));
|
|
97
|
+
},
|
|
98
|
+
applyUserActionState(key, updater) {
|
|
99
|
+
const entry = streams.get(key);
|
|
100
|
+
if (!entry) return;
|
|
101
|
+
const next = typeof updater === "function" ? updater(entry.userActionState) : updater;
|
|
102
|
+
entry.userActionState = next;
|
|
103
|
+
entry.listeners.forEach((l) => l(entry.messages, entry.isWaiting, next));
|
|
91
104
|
},
|
|
92
105
|
// Called when stream completes — persists to chatStore and cleans up
|
|
93
106
|
complete(key) {
|
|
94
107
|
const entry = streams.get(key);
|
|
95
108
|
if (!entry) return;
|
|
96
109
|
entry.isWaiting = false;
|
|
97
|
-
entry.listeners.forEach((l) => l(entry.messages, false));
|
|
110
|
+
entry.listeners.forEach((l) => l(entry.messages, false, entry.userActionState));
|
|
98
111
|
const toSave = entry.messages.filter((m) => !m.isStreaming);
|
|
99
112
|
if (toSave.length > 0) chatStore.set(key, toSave);
|
|
100
113
|
streams.delete(key);
|
|
@@ -731,8 +744,7 @@ function buildRequestBody(config, userMessage, sessionId, options) {
|
|
|
731
744
|
sessionAttributes,
|
|
732
745
|
analysisMode: options?.analysisMode,
|
|
733
746
|
locale: resolveLocale(config.locale),
|
|
734
|
-
timezone: resolveTimezone(config.timezone)
|
|
735
|
-
...options?.attachments?.length ? { attachments: options.attachments } : {}
|
|
747
|
+
timezone: resolveTimezone(config.timezone)
|
|
736
748
|
};
|
|
737
749
|
}
|
|
738
750
|
function resolveLocale(configured) {
|
|
@@ -777,7 +789,6 @@ function buildResolveImagesUrl(config) {
|
|
|
777
789
|
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
778
790
|
return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
|
|
779
791
|
}
|
|
780
|
-
var NGROK_SKIP_BROWSER_WARNING = "ngrok-skip-browser-warning";
|
|
781
792
|
function buildRequestHeaders(config) {
|
|
782
793
|
const headers = {
|
|
783
794
|
...config.api.headers
|
|
@@ -785,9 +796,6 @@ function buildRequestHeaders(config) {
|
|
|
785
796
|
if (config.api.authToken) {
|
|
786
797
|
headers.Authorization = `Bearer ${config.api.authToken}`;
|
|
787
798
|
}
|
|
788
|
-
if (!headers[NGROK_SKIP_BROWSER_WARNING]) {
|
|
789
|
-
headers[NGROK_SKIP_BROWSER_WARNING] = "true";
|
|
790
|
-
}
|
|
791
799
|
return headers;
|
|
792
800
|
}
|
|
793
801
|
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
@@ -1068,81 +1076,6 @@ function createCancelledMessageUpdate(steps, currentMessage) {
|
|
|
1068
1076
|
currentMessage: currentMessage || "Thinking..."
|
|
1069
1077
|
};
|
|
1070
1078
|
}
|
|
1071
|
-
var DEFAULT_SIGNED_URL_ENDPOINT = "/api/files/signed-url";
|
|
1072
|
-
function fileExtension(filename) {
|
|
1073
|
-
const ext = filename.split(".").pop()?.toLowerCase().replace(/^\./, "");
|
|
1074
|
-
return ext && ext.length > 0 ? ext : "bin";
|
|
1075
|
-
}
|
|
1076
|
-
function resolveMimeType(file) {
|
|
1077
|
-
if (file.type && file.type.trim().length > 0) return file.type;
|
|
1078
|
-
const ext = fileExtension(file.name);
|
|
1079
|
-
const byExt = {
|
|
1080
|
-
pdf: "application/pdf",
|
|
1081
|
-
png: "image/png",
|
|
1082
|
-
jpg: "image/jpeg",
|
|
1083
|
-
jpeg: "image/jpeg",
|
|
1084
|
-
gif: "image/gif",
|
|
1085
|
-
webp: "image/webp"
|
|
1086
|
-
};
|
|
1087
|
-
return byExt[ext] ?? "application/octet-stream";
|
|
1088
|
-
}
|
|
1089
|
-
function buildSignedUrlEndpoint(config, ext) {
|
|
1090
|
-
const endpoint = config.api.signedUrlEndpoint || DEFAULT_SIGNED_URL_ENDPOINT;
|
|
1091
|
-
const queryParams = new URLSearchParams({ extn: ext.replace(/^\./, "") });
|
|
1092
|
-
return `${config.api.baseUrl}${endpoint}?${queryParams.toString()}`;
|
|
1093
|
-
}
|
|
1094
|
-
async function requestSignedUrl(config, ext, signal) {
|
|
1095
|
-
const url = buildSignedUrlEndpoint(config, ext);
|
|
1096
|
-
const headers = buildRequestHeaders(config);
|
|
1097
|
-
const response = await fetch(url, {
|
|
1098
|
-
method: "GET",
|
|
1099
|
-
headers,
|
|
1100
|
-
signal
|
|
1101
|
-
});
|
|
1102
|
-
if (!response.ok) {
|
|
1103
|
-
const errorText = await response.text().catch(() => "");
|
|
1104
|
-
throw new Error(
|
|
1105
|
-
errorText ? `Failed to get upload URL (${response.status}): ${errorText}` : `Failed to get upload URL (${response.status})`
|
|
1106
|
-
);
|
|
1107
|
-
}
|
|
1108
|
-
const data = await response.json();
|
|
1109
|
-
if (!data.key || !data.url) {
|
|
1110
|
-
throw new Error("Signed URL response missing key or url");
|
|
1111
|
-
}
|
|
1112
|
-
return { key: data.key, url: data.url };
|
|
1113
|
-
}
|
|
1114
|
-
async function uploadToSignedUrl(signedUrl, file, mimeType, signal) {
|
|
1115
|
-
const response = await fetch(signedUrl, {
|
|
1116
|
-
method: "PUT",
|
|
1117
|
-
headers: {
|
|
1118
|
-
"Content-Type": mimeType,
|
|
1119
|
-
"x-ms-blob-type": "BlockBlob"
|
|
1120
|
-
},
|
|
1121
|
-
body: file,
|
|
1122
|
-
signal
|
|
1123
|
-
});
|
|
1124
|
-
if (!response.ok) {
|
|
1125
|
-
const errorText = await response.text().catch(() => "");
|
|
1126
|
-
throw new Error(
|
|
1127
|
-
errorText ? `Failed to upload file (${response.status}): ${errorText}` : `Failed to upload file (${response.status})`
|
|
1128
|
-
);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
async function uploadAttachment(config, file, signal) {
|
|
1132
|
-
const ext = fileExtension(file.name);
|
|
1133
|
-
const mimeType = resolveMimeType(file);
|
|
1134
|
-
const { key, url } = await requestSignedUrl(config, ext, signal);
|
|
1135
|
-
await uploadToSignedUrl(url, file, mimeType, signal);
|
|
1136
|
-
return {
|
|
1137
|
-
tempKey: key,
|
|
1138
|
-
filename: file.name,
|
|
1139
|
-
mimeType
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
async function uploadAttachments(config, files, signal) {
|
|
1143
|
-
const uploads = files.map((file) => uploadAttachment(config, file, signal));
|
|
1144
|
-
return Promise.all(uploads);
|
|
1145
|
-
}
|
|
1146
1079
|
var UserActionStaleError = class extends Error {
|
|
1147
1080
|
constructor(userActionId, message = "User action is no longer actionable") {
|
|
1148
1081
|
super(message);
|
|
@@ -1179,12 +1112,38 @@ async function cancelUserAction(config, userActionId) {
|
|
|
1179
1112
|
async function resendUserAction(config, userActionId) {
|
|
1180
1113
|
return sendUserActionRequest(config, userActionId, "resend");
|
|
1181
1114
|
}
|
|
1182
|
-
|
|
1115
|
+
async function expireUserAction(config, userActionId) {
|
|
1116
|
+
return sendUserActionRequest(config, userActionId, "expired");
|
|
1117
|
+
}
|
|
1118
|
+
function resolveUserActionExpiresAt(req, existing) {
|
|
1119
|
+
if (typeof req.expirySeconds !== "number" || req.expirySeconds <= 0) {
|
|
1120
|
+
return void 0;
|
|
1121
|
+
}
|
|
1122
|
+
if (existing?.expiresAt && existing.userActionId === req.userActionId) {
|
|
1123
|
+
return existing.expiresAt;
|
|
1124
|
+
}
|
|
1125
|
+
return Date.now() + Math.floor(req.expirySeconds) * 1e3;
|
|
1126
|
+
}
|
|
1127
|
+
function getUserActionSecondsLeft(prompt) {
|
|
1128
|
+
if (typeof prompt.expiresAt === "number" && prompt.expiresAt > 0) {
|
|
1129
|
+
return Math.max(0, Math.ceil((prompt.expiresAt - Date.now()) / 1e3));
|
|
1130
|
+
}
|
|
1131
|
+
if (typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0) {
|
|
1132
|
+
return Math.floor(prompt.expirySeconds);
|
|
1133
|
+
}
|
|
1134
|
+
return void 0;
|
|
1135
|
+
}
|
|
1136
|
+
function isUserActionExpired(prompt) {
|
|
1137
|
+
const left = getUserActionSecondsLeft(prompt);
|
|
1138
|
+
return left !== void 0 && left <= 0;
|
|
1139
|
+
}
|
|
1140
|
+
var EMPTY_USER_ACTION_STATE2 = { prompts: [], notifications: [] };
|
|
1183
1141
|
function upsertPrompt(prompts, req) {
|
|
1184
|
-
const active = { ...req, status: "pending" };
|
|
1185
1142
|
const idx = prompts.findIndex(
|
|
1186
1143
|
(p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
|
|
1187
1144
|
);
|
|
1145
|
+
const expiresAt = resolveUserActionExpiresAt(req, idx >= 0 ? prompts[idx] : void 0);
|
|
1146
|
+
const active = { ...req, status: "pending", expiresAt };
|
|
1188
1147
|
if (idx >= 0) {
|
|
1189
1148
|
const next = prompts.slice();
|
|
1190
1149
|
next[idx] = active;
|
|
@@ -1207,32 +1166,12 @@ function getStoredOrInitialMessages(config) {
|
|
|
1207
1166
|
function getSessionIdFromMessages(messages) {
|
|
1208
1167
|
return messages.find((message) => message.sessionId)?.sessionId;
|
|
1209
1168
|
}
|
|
1210
|
-
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["jpg", "jpeg", "png", "gif", "webp", "avif"]);
|
|
1211
|
-
function attachmentKindFromFile(file) {
|
|
1212
|
-
const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
|
|
1213
|
-
if (IMAGE_EXTENSIONS.has(ext) || file.type.startsWith("image/")) return "image";
|
|
1214
|
-
return "file";
|
|
1215
|
-
}
|
|
1216
|
-
function buildMessageAttachments(files) {
|
|
1217
|
-
return files.map((file, index) => {
|
|
1218
|
-
const kind = attachmentKindFromFile(file);
|
|
1219
|
-
return {
|
|
1220
|
-
id: `att-${Date.now()}-${index}`,
|
|
1221
|
-
filename: file.name,
|
|
1222
|
-
mimeType: file.type || "application/octet-stream",
|
|
1223
|
-
// Blob URL for all local files so PDFs/docs stay previewable after send.
|
|
1224
|
-
previewUrl: URL.createObjectURL(file),
|
|
1225
|
-
kind
|
|
1226
|
-
};
|
|
1227
|
-
});
|
|
1228
|
-
}
|
|
1229
1169
|
function useChatV2(config, callbacks = {}) {
|
|
1230
1170
|
const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
|
|
1231
1171
|
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
|
|
1232
1172
|
if (!config.userId) return false;
|
|
1233
1173
|
return activeStreamStore.get(config.userId)?.isWaiting ?? false;
|
|
1234
1174
|
});
|
|
1235
|
-
const [isUploadingAttachments, setIsUploadingAttachments] = react.useState(false);
|
|
1236
1175
|
const sessionIdRef = react.useRef(
|
|
1237
1176
|
getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
|
|
1238
1177
|
);
|
|
@@ -1275,7 +1214,28 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1275
1214
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1276
1215
|
[]
|
|
1277
1216
|
);
|
|
1278
|
-
const [userActionState, setUserActionState] = react.useState(
|
|
1217
|
+
const [userActionState, setUserActionState] = react.useState(() => {
|
|
1218
|
+
if (!config.userId) return EMPTY_USER_ACTION_STATE2;
|
|
1219
|
+
return activeStreamStore.get(config.userId)?.userActionState ?? EMPTY_USER_ACTION_STATE2;
|
|
1220
|
+
});
|
|
1221
|
+
const storeAwareSetUserActionState = react.useCallback(
|
|
1222
|
+
(updater) => {
|
|
1223
|
+
const streamUserId = streamUserIdRef.current;
|
|
1224
|
+
const currentUserId = configRef.current.userId;
|
|
1225
|
+
const storeKey = streamUserId ?? currentUserId;
|
|
1226
|
+
if (storeKey && activeStreamStore.has(storeKey)) {
|
|
1227
|
+
activeStreamStore.applyUserActionState(
|
|
1228
|
+
storeKey,
|
|
1229
|
+
updater
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
if (!streamUserId || streamUserId === currentUserId) {
|
|
1233
|
+
setUserActionState(updater);
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1237
|
+
[]
|
|
1238
|
+
);
|
|
1279
1239
|
const wrappedCallbacks = react.useMemo(() => ({
|
|
1280
1240
|
...callbacksRef.current,
|
|
1281
1241
|
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
@@ -1285,14 +1245,14 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1285
1245
|
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1286
1246
|
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1287
1247
|
onUserActionRequired: (request) => {
|
|
1288
|
-
|
|
1248
|
+
storeAwareSetUserActionState((prev) => ({
|
|
1289
1249
|
...prev,
|
|
1290
1250
|
prompts: upsertPrompt(prev.prompts, request)
|
|
1291
1251
|
}));
|
|
1292
1252
|
callbacksRef.current.onUserActionRequired?.(request);
|
|
1293
1253
|
},
|
|
1294
1254
|
onUserNotification: (notification) => {
|
|
1295
|
-
|
|
1255
|
+
storeAwareSetUserActionState(
|
|
1296
1256
|
(prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
|
|
1297
1257
|
);
|
|
1298
1258
|
callbacksRef.current.onUserNotification?.(notification);
|
|
@@ -1307,45 +1267,21 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1307
1267
|
);
|
|
1308
1268
|
const sendMessage = react.useCallback(
|
|
1309
1269
|
async (userMessage, options) => {
|
|
1310
|
-
|
|
1311
|
-
const files = options?.files ?? [];
|
|
1312
|
-
const hasPreuploadedAttachments = (options?.attachments?.length ?? 0) > 0;
|
|
1313
|
-
if (!trimmedMessage && files.length === 0 && !hasPreuploadedAttachments) return;
|
|
1314
|
-
let streamAttachments = options?.attachments;
|
|
1315
|
-
if (!streamAttachments && files.length > 0) {
|
|
1316
|
-
setIsUploadingAttachments(true);
|
|
1317
|
-
try {
|
|
1318
|
-
streamAttachments = await uploadAttachments(configRef.current, files);
|
|
1319
|
-
} catch (error) {
|
|
1320
|
-
if (error.name !== "AbortError") {
|
|
1321
|
-
callbacksRef.current.onError?.(error);
|
|
1322
|
-
}
|
|
1323
|
-
throw error;
|
|
1324
|
-
} finally {
|
|
1325
|
-
setIsUploadingAttachments(false);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1270
|
+
if (!userMessage.trim()) return;
|
|
1328
1271
|
if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
|
|
1329
1272
|
sessionIdRef.current = generateId();
|
|
1330
1273
|
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
1331
1274
|
}
|
|
1332
|
-
const messageAttachments = files.length > 0 ? buildMessageAttachments(files) : streamAttachments?.length ? streamAttachments.map((attachment, index) => ({
|
|
1333
|
-
id: `att-${Date.now()}-${index}`,
|
|
1334
|
-
filename: attachment.filename,
|
|
1335
|
-
mimeType: attachment.mimeType,
|
|
1336
|
-
kind: attachment.mimeType.startsWith("image/") ? "image" : "file"
|
|
1337
|
-
})) : void 0;
|
|
1338
1275
|
const userMessageId = `user-${Date.now()}`;
|
|
1339
1276
|
const userMsg = {
|
|
1340
1277
|
id: userMessageId,
|
|
1341
1278
|
sessionId: sessionIdRef.current,
|
|
1342
1279
|
role: "user",
|
|
1343
|
-
content:
|
|
1344
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1345
|
-
attachments: messageAttachments
|
|
1280
|
+
content: userMessage,
|
|
1281
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1346
1282
|
};
|
|
1347
1283
|
setMessages((prev) => [...prev, userMsg]);
|
|
1348
|
-
callbacksRef.current.onMessageSent?.(
|
|
1284
|
+
callbacksRef.current.onMessageSent?.(userMessage);
|
|
1349
1285
|
setIsWaitingForResponse(true);
|
|
1350
1286
|
callbacksRef.current.onStreamStart?.();
|
|
1351
1287
|
const streamingId = `assistant-${Date.now()}`;
|
|
@@ -1372,14 +1308,11 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1372
1308
|
activeStreamStore.start(userId, abortController, initialMessages);
|
|
1373
1309
|
}
|
|
1374
1310
|
const newSessionId = await startStream(
|
|
1375
|
-
|
|
1311
|
+
userMessage,
|
|
1376
1312
|
streamingId,
|
|
1377
1313
|
sessionIdRef.current,
|
|
1378
1314
|
abortController,
|
|
1379
|
-
|
|
1380
|
-
analysisMode: options?.analysisMode,
|
|
1381
|
-
attachments: streamAttachments
|
|
1382
|
-
}
|
|
1315
|
+
options
|
|
1383
1316
|
);
|
|
1384
1317
|
const finalStreamUserId = streamUserIdRef.current ?? userId;
|
|
1385
1318
|
if (finalStreamUserId) {
|
|
@@ -1409,7 +1342,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1409
1342
|
streamUserIdRef.current = void 0;
|
|
1410
1343
|
cancelStreamManager();
|
|
1411
1344
|
setIsWaitingForResponse(false);
|
|
1412
|
-
|
|
1345
|
+
storeAwareSetUserActionState((prev) => ({ ...prev, prompts: [] }));
|
|
1413
1346
|
setMessages(
|
|
1414
1347
|
(prev) => prev.map((msg) => {
|
|
1415
1348
|
if (msg.isStreaming) {
|
|
@@ -1424,7 +1357,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1424
1357
|
return msg;
|
|
1425
1358
|
})
|
|
1426
1359
|
);
|
|
1427
|
-
}, [cancelStreamManager]);
|
|
1360
|
+
}, [cancelStreamManager, storeAwareSetUserActionState]);
|
|
1428
1361
|
const resetSession = react.useCallback(() => {
|
|
1429
1362
|
const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
|
|
1430
1363
|
if (streamUserId) {
|
|
@@ -1438,7 +1371,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1438
1371
|
sessionIdRef.current = void 0;
|
|
1439
1372
|
abortControllerRef.current?.abort();
|
|
1440
1373
|
setIsWaitingForResponse(false);
|
|
1441
|
-
|
|
1374
|
+
storeAwareSetUserActionState(EMPTY_USER_ACTION_STATE2);
|
|
1442
1375
|
}, []);
|
|
1443
1376
|
const getSessionId = react.useCallback(() => {
|
|
1444
1377
|
return sessionIdRef.current;
|
|
@@ -1448,21 +1381,21 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1448
1381
|
}, [messages]);
|
|
1449
1382
|
const setPromptStatus = react.useCallback(
|
|
1450
1383
|
(userActionId, status) => {
|
|
1451
|
-
|
|
1384
|
+
storeAwareSetUserActionState((prev) => ({
|
|
1452
1385
|
...prev,
|
|
1453
1386
|
prompts: prev.prompts.map(
|
|
1454
1387
|
(p) => p.userActionId === userActionId ? { ...p, status } : p
|
|
1455
1388
|
)
|
|
1456
1389
|
}));
|
|
1457
1390
|
},
|
|
1458
|
-
[]
|
|
1391
|
+
[storeAwareSetUserActionState]
|
|
1459
1392
|
);
|
|
1460
1393
|
const removePrompt = react.useCallback((userActionId) => {
|
|
1461
|
-
|
|
1394
|
+
storeAwareSetUserActionState((prev) => ({
|
|
1462
1395
|
...prev,
|
|
1463
1396
|
prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
|
|
1464
1397
|
}));
|
|
1465
|
-
}, []);
|
|
1398
|
+
}, [storeAwareSetUserActionState]);
|
|
1466
1399
|
const submitUserAction2 = react.useCallback(
|
|
1467
1400
|
async (userActionId, content) => {
|
|
1468
1401
|
setPromptStatus(userActionId, "submitting");
|
|
@@ -1517,12 +1450,25 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1517
1450
|
},
|
|
1518
1451
|
[setPromptStatus]
|
|
1519
1452
|
);
|
|
1453
|
+
const expireUserAction2 = react.useCallback(
|
|
1454
|
+
async (userActionId) => {
|
|
1455
|
+
setPromptStatus(userActionId, "expired");
|
|
1456
|
+
try {
|
|
1457
|
+
await expireUserAction(configRef.current, userActionId);
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
if (error instanceof UserActionStaleError) return;
|
|
1460
|
+
callbacksRef.current.onError?.(error);
|
|
1461
|
+
throw error;
|
|
1462
|
+
}
|
|
1463
|
+
},
|
|
1464
|
+
[setPromptStatus]
|
|
1465
|
+
);
|
|
1520
1466
|
const dismissNotification = react.useCallback((id) => {
|
|
1521
|
-
|
|
1467
|
+
storeAwareSetUserActionState((prev) => ({
|
|
1522
1468
|
...prev,
|
|
1523
1469
|
notifications: prev.notifications.filter((n) => n.id !== id)
|
|
1524
1470
|
}));
|
|
1525
|
-
}, []);
|
|
1471
|
+
}, [storeAwareSetUserActionState]);
|
|
1526
1472
|
react.useEffect(() => {
|
|
1527
1473
|
const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
|
|
1528
1474
|
subscriptionPrevUserIdRef.current = config.userId;
|
|
@@ -1531,14 +1477,17 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1531
1477
|
if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
|
|
1532
1478
|
streamUserIdRef.current = userId;
|
|
1533
1479
|
}
|
|
1534
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1480
|
+
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting, actions) => {
|
|
1535
1481
|
setMessages(msgs);
|
|
1536
1482
|
setIsWaitingForResponse(isWaiting);
|
|
1483
|
+
setUserActionState(actions);
|
|
1537
1484
|
});
|
|
1538
1485
|
const active = activeStreamStore.get(userId);
|
|
1539
1486
|
if (active) {
|
|
1487
|
+
streamUserIdRef.current = userId;
|
|
1540
1488
|
setMessages(active.messages);
|
|
1541
1489
|
setIsWaitingForResponse(active.isWaiting);
|
|
1490
|
+
setUserActionState(active.userActionState);
|
|
1542
1491
|
}
|
|
1543
1492
|
return unsubscribe;
|
|
1544
1493
|
}, [config.userId]);
|
|
@@ -1557,18 +1506,6 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1557
1506
|
setMessages(config.initialMessages);
|
|
1558
1507
|
sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
|
|
1559
1508
|
}, [config.initialMessages, config.initialSessionId, config.userId]);
|
|
1560
|
-
const hydratedSessionIdRef = react.useRef(void 0);
|
|
1561
|
-
react.useEffect(() => {
|
|
1562
|
-
if (config.userId) return;
|
|
1563
|
-
if (!config.initialMessages?.length) return;
|
|
1564
|
-
const sessionId = config.initialSessionId;
|
|
1565
|
-
if (hydratedSessionIdRef.current === sessionId && messagesRef.current.length > 0) {
|
|
1566
|
-
return;
|
|
1567
|
-
}
|
|
1568
|
-
hydratedSessionIdRef.current = sessionId;
|
|
1569
|
-
setMessages(config.initialMessages);
|
|
1570
|
-
sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? sessionId;
|
|
1571
|
-
}, [config.initialMessages, config.initialSessionId, config.userId]);
|
|
1572
1509
|
react.useEffect(() => {
|
|
1573
1510
|
const prevUserId = prevUserIdRef.current;
|
|
1574
1511
|
prevUserIdRef.current = config.userId;
|
|
@@ -1578,12 +1515,14 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1578
1515
|
setMessages([]);
|
|
1579
1516
|
sessionIdRef.current = void 0;
|
|
1580
1517
|
setIsWaitingForResponse(false);
|
|
1581
|
-
setUserActionState(
|
|
1518
|
+
setUserActionState(EMPTY_USER_ACTION_STATE2);
|
|
1582
1519
|
} else if (config.userId) {
|
|
1583
1520
|
const active = activeStreamStore.get(config.userId);
|
|
1584
1521
|
if (active) {
|
|
1522
|
+
streamUserIdRef.current = config.userId;
|
|
1585
1523
|
setMessages(active.messages);
|
|
1586
1524
|
setIsWaitingForResponse(active.isWaiting);
|
|
1525
|
+
setUserActionState(active.userActionState);
|
|
1587
1526
|
sessionIdRef.current = getSessionIdFromMessages(active.messages) ?? config.initialSessionId;
|
|
1588
1527
|
return;
|
|
1589
1528
|
}
|
|
@@ -1603,12 +1542,12 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1603
1542
|
getSessionId,
|
|
1604
1543
|
getMessages,
|
|
1605
1544
|
isWaitingForResponse,
|
|
1606
|
-
isUploadingAttachments,
|
|
1607
1545
|
sessionId: sessionIdRef.current,
|
|
1608
1546
|
userActionState,
|
|
1609
1547
|
submitUserAction: submitUserAction2,
|
|
1610
1548
|
cancelUserAction: cancelUserAction2,
|
|
1611
1549
|
resendUserAction: resendUserAction2,
|
|
1550
|
+
expireUserAction: expireUserAction2,
|
|
1612
1551
|
dismissNotification
|
|
1613
1552
|
};
|
|
1614
1553
|
}
|
|
@@ -2571,35 +2510,37 @@ function uaPalette(isDark) {
|
|
|
2571
2510
|
grabber: "rgba(15,23,42,0.18)"
|
|
2572
2511
|
};
|
|
2573
2512
|
}
|
|
2574
|
-
function promptInitialSeconds(prompt) {
|
|
2575
|
-
return prompt && typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Math.floor(prompt.expirySeconds) : void 0;
|
|
2576
|
-
}
|
|
2577
2513
|
function promptTimerKey(prompt) {
|
|
2578
|
-
return prompt ? `${prompt.userActionId}|${prompt.subAction ?? ""}` : "";
|
|
2514
|
+
return prompt ? `${prompt.userActionId}|${prompt.subAction ?? ""}|${prompt.expiresAt ?? ""}` : "";
|
|
2579
2515
|
}
|
|
2580
2516
|
function useExpiredFlag(prompt) {
|
|
2581
|
-
const initial = promptInitialSeconds(prompt);
|
|
2582
2517
|
const key = promptTimerKey(prompt);
|
|
2583
|
-
const [expired, setExpired] = react.useState(false);
|
|
2518
|
+
const [expired, setExpired] = react.useState(() => prompt ? isUserActionExpired(prompt) : false);
|
|
2584
2519
|
react.useEffect(() => {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2520
|
+
const secondsLeft = prompt ? getUserActionSecondsLeft(prompt) : void 0;
|
|
2521
|
+
setExpired(secondsLeft !== void 0 && secondsLeft <= 0);
|
|
2522
|
+
if (secondsLeft === void 0 || secondsLeft <= 0) return;
|
|
2523
|
+
const t = setTimeout(() => setExpired(true), secondsLeft * 1e3);
|
|
2588
2524
|
return () => clearTimeout(t);
|
|
2589
|
-
}, [key,
|
|
2525
|
+
}, [key, prompt]);
|
|
2590
2526
|
return expired;
|
|
2591
2527
|
}
|
|
2592
|
-
function useSecondsLeft(
|
|
2593
|
-
const
|
|
2594
|
-
const [left, setLeft] = react.useState(
|
|
2528
|
+
function useSecondsLeft(prompt, restartKey) {
|
|
2529
|
+
const deadlineMs = prompt && typeof prompt.expiresAt === "number" && prompt.expiresAt > 0 ? prompt.expiresAt : prompt && typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Date.now() + Math.floor(prompt.expirySeconds) * 1e3 : void 0;
|
|
2530
|
+
const [left, setLeft] = react.useState(
|
|
2531
|
+
() => deadlineMs !== void 0 ? Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3)) : void 0
|
|
2532
|
+
);
|
|
2595
2533
|
react.useEffect(() => {
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2534
|
+
if (deadlineMs === void 0) {
|
|
2535
|
+
setLeft(void 0);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
const remaining = () => Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3));
|
|
2539
|
+
const sync = () => setLeft(remaining());
|
|
2540
|
+
sync();
|
|
2541
|
+
const id = setInterval(sync, 1e3);
|
|
2601
2542
|
return () => clearInterval(id);
|
|
2602
|
-
}, [restartKey,
|
|
2543
|
+
}, [restartKey, deadlineMs]);
|
|
2603
2544
|
return left;
|
|
2604
2545
|
}
|
|
2605
2546
|
function SheetTimerPill({
|
|
@@ -2607,7 +2548,7 @@ function SheetTimerPill({
|
|
|
2607
2548
|
accent,
|
|
2608
2549
|
isDark
|
|
2609
2550
|
}) {
|
|
2610
|
-
const left = useSecondsLeft(prompt
|
|
2551
|
+
const left = useSecondsLeft(prompt, promptTimerKey(prompt));
|
|
2611
2552
|
if (left === void 0 || prompt.status === "stale") return null;
|
|
2612
2553
|
const expired = left <= 0;
|
|
2613
2554
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2629,7 +2570,7 @@ function CardTimerPill({
|
|
|
2629
2570
|
accent,
|
|
2630
2571
|
isDark
|
|
2631
2572
|
}) {
|
|
2632
|
-
const left = useSecondsLeft(prompt
|
|
2573
|
+
const left = useSecondsLeft(prompt, promptTimerKey(prompt));
|
|
2633
2574
|
if (left === void 0 || left <= 0) return null;
|
|
2634
2575
|
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [rc.timer, { backgroundColor: accent + (isDark ? "26" : "14") }], children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: { color: accent, fontSize: 12, fontWeight: "700" }, children: [
|
|
2635
2576
|
left,
|