@paymanai/payman-ask-sdk 4.0.24 → 4.0.26
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 +503 -439
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +247 -183
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +154 -192
- package/dist/index.native.js.map +1 -1
- package/dist/styles.css +30 -0
- package/dist/styles.css.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);
|
|
@@ -282,6 +295,8 @@ function getEventMessage(event) {
|
|
|
282
295
|
}
|
|
283
296
|
case "RUN_IN_PROGRESS":
|
|
284
297
|
return event.partialText || "Thinking...";
|
|
298
|
+
case "LLM_CALL_STARTED":
|
|
299
|
+
return event.description || "";
|
|
285
300
|
case "RUN_COMPLETED":
|
|
286
301
|
return "Agent run completed";
|
|
287
302
|
case "RUN_FAILED":
|
|
@@ -419,7 +434,8 @@ function createInitialV2State() {
|
|
|
419
434
|
finalData: void 0,
|
|
420
435
|
steps: [],
|
|
421
436
|
stepCounter: 0,
|
|
422
|
-
currentExecutingStepId: void 0
|
|
437
|
+
currentExecutingStepId: void 0,
|
|
438
|
+
trailingActivity: false
|
|
423
439
|
};
|
|
424
440
|
}
|
|
425
441
|
function upsertUserAction(state, req) {
|
|
@@ -441,6 +457,7 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
441
457
|
if (typeof eventType === "string" && eventType.toUpperCase() === "KEEP_ALIVE") {
|
|
442
458
|
if (event.executionId) state.executionId = event.executionId;
|
|
443
459
|
if (event.sessionId) state.sessionId = event.sessionId;
|
|
460
|
+
if (state.finalResponse) state.trailingActivity = true;
|
|
444
461
|
const description = typeof event.description === "string" ? event.description : "";
|
|
445
462
|
if (description) {
|
|
446
463
|
for (let i = state.steps.length - 1; i >= 0; i--) {
|
|
@@ -475,7 +492,12 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
475
492
|
state.lastEventType = eventType;
|
|
476
493
|
break;
|
|
477
494
|
}
|
|
495
|
+
case "LLM_CALL_STARTED":
|
|
496
|
+
if (state.finalResponse) state.trailingActivity = true;
|
|
497
|
+
state.lastEventType = eventType;
|
|
498
|
+
break;
|
|
478
499
|
case "INTENT_STARTED": {
|
|
500
|
+
if (state.finalResponse) state.trailingActivity = true;
|
|
479
501
|
const stepId = `step-${state.stepCounter++}`;
|
|
480
502
|
state.steps.push({
|
|
481
503
|
id: stepId,
|
|
@@ -491,6 +513,7 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
491
513
|
break;
|
|
492
514
|
}
|
|
493
515
|
case "INTENT_COMPLETED": {
|
|
516
|
+
if (state.finalResponse) state.trailingActivity = true;
|
|
494
517
|
const intentStep = state.steps.find((s2) => s2.eventType === "INTENT_STARTED" && s2.status === "in_progress");
|
|
495
518
|
if (intentStep) {
|
|
496
519
|
intentStep.status = "completed";
|
|
@@ -522,6 +545,7 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
522
545
|
step2.status = "completed";
|
|
523
546
|
}
|
|
524
547
|
});
|
|
548
|
+
state.trailingActivity = false;
|
|
525
549
|
state.lastEventType = eventType;
|
|
526
550
|
break;
|
|
527
551
|
}
|
|
@@ -593,6 +617,7 @@ function processStreamEventV2(rawEvent, state) {
|
|
|
593
617
|
case "WORKFLOW_ERROR":
|
|
594
618
|
state.hasError = true;
|
|
595
619
|
state.errorMessage = event.errorMessage || event.message || "Workflow error";
|
|
620
|
+
state.trailingActivity = false;
|
|
596
621
|
state.lastEventType = eventType;
|
|
597
622
|
break;
|
|
598
623
|
// ---- K2 pipeline stage lifecycle events ----
|
|
@@ -731,8 +756,7 @@ function buildRequestBody(config, userMessage, sessionId, options) {
|
|
|
731
756
|
sessionAttributes,
|
|
732
757
|
analysisMode: options?.analysisMode,
|
|
733
758
|
locale: resolveLocale(config.locale),
|
|
734
|
-
timezone: resolveTimezone(config.timezone)
|
|
735
|
-
...options?.attachments?.length ? { attachments: options.attachments } : {}
|
|
759
|
+
timezone: resolveTimezone(config.timezone)
|
|
736
760
|
};
|
|
737
761
|
}
|
|
738
762
|
function resolveLocale(configured) {
|
|
@@ -777,7 +801,6 @@ function buildResolveImagesUrl(config) {
|
|
|
777
801
|
const basePath = normalizedEndpointPath.endsWith("/stream") ? normalizedEndpointPath.slice(0, -"/stream".length) : normalizedEndpointPath;
|
|
778
802
|
return `${config.api.baseUrl}${basePath}/resolve-image-urls`;
|
|
779
803
|
}
|
|
780
|
-
var NGROK_SKIP_BROWSER_WARNING = "ngrok-skip-browser-warning";
|
|
781
804
|
function buildRequestHeaders(config) {
|
|
782
805
|
const headers = {
|
|
783
806
|
...config.api.headers
|
|
@@ -785,9 +808,6 @@ function buildRequestHeaders(config) {
|
|
|
785
808
|
if (config.api.authToken) {
|
|
786
809
|
headers.Authorization = `Bearer ${config.api.authToken}`;
|
|
787
810
|
}
|
|
788
|
-
if (!headers[NGROK_SKIP_BROWSER_WARNING]) {
|
|
789
|
-
headers[NGROK_SKIP_BROWSER_WARNING] = "true";
|
|
790
|
-
}
|
|
791
811
|
return headers;
|
|
792
812
|
}
|
|
793
813
|
var RAG_IMAGE_REGEX = /\/api\/rag\/chunks\/[^"'\s]+\/image/;
|
|
@@ -886,7 +906,9 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
886
906
|
const latestUsefulStep = [...state.steps].reverse().find(
|
|
887
907
|
(s2) => s2.message && !isBlandStatus(s2.message)
|
|
888
908
|
);
|
|
889
|
-
const
|
|
909
|
+
const eventTypeUpper = typeof event.eventType === "string" ? event.eventType.toUpperCase() : "";
|
|
910
|
+
const liveEventLabel = eventTypeUpper === "KEEP_ALIVE" || eventTypeUpper === "LLM_CALL_STARTED" ? useful(getEventMessage(event)) : void 0;
|
|
911
|
+
const currentMessage = useful(activeStep?.message) ?? useful(lastInProgressStep?.message) ?? liveEventLabel ?? latestUsefulStep?.message ?? useful(getEventMessage(event)) ?? // Last-resort: every candidate is bland (very first event,
|
|
890
912
|
// nothing useful seen yet). Render the bland label so the
|
|
891
913
|
// bubble isn't blank.
|
|
892
914
|
activeStep?.message ?? lastInProgressStep?.message ?? getEventMessage(event);
|
|
@@ -911,6 +933,7 @@ function useStreamManagerV2(config, callbacks, setMessages, setIsWaitingForRespo
|
|
|
911
933
|
streamingContent: state.finalResponse,
|
|
912
934
|
content: "",
|
|
913
935
|
currentMessage,
|
|
936
|
+
hasTrailingActivity: state.trailingActivity,
|
|
914
937
|
streamProgress: "processing",
|
|
915
938
|
isError: false,
|
|
916
939
|
steps: [...state.steps],
|
|
@@ -1068,81 +1091,6 @@ function createCancelledMessageUpdate(steps, currentMessage) {
|
|
|
1068
1091
|
currentMessage: currentMessage || "Thinking..."
|
|
1069
1092
|
};
|
|
1070
1093
|
}
|
|
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
1094
|
var UserActionStaleError = class extends Error {
|
|
1147
1095
|
constructor(userActionId, message = "User action is no longer actionable") {
|
|
1148
1096
|
super(message);
|
|
@@ -1179,12 +1127,38 @@ async function cancelUserAction(config, userActionId) {
|
|
|
1179
1127
|
async function resendUserAction(config, userActionId) {
|
|
1180
1128
|
return sendUserActionRequest(config, userActionId, "resend");
|
|
1181
1129
|
}
|
|
1182
|
-
|
|
1130
|
+
async function expireUserAction(config, userActionId) {
|
|
1131
|
+
return sendUserActionRequest(config, userActionId, "expired");
|
|
1132
|
+
}
|
|
1133
|
+
function resolveUserActionExpiresAt(req, existing) {
|
|
1134
|
+
if (typeof req.expirySeconds !== "number" || req.expirySeconds <= 0) {
|
|
1135
|
+
return void 0;
|
|
1136
|
+
}
|
|
1137
|
+
if (existing?.expiresAt && existing.userActionId === req.userActionId) {
|
|
1138
|
+
return existing.expiresAt;
|
|
1139
|
+
}
|
|
1140
|
+
return Date.now() + Math.floor(req.expirySeconds) * 1e3;
|
|
1141
|
+
}
|
|
1142
|
+
function getUserActionSecondsLeft(prompt) {
|
|
1143
|
+
if (typeof prompt.expiresAt === "number" && prompt.expiresAt > 0) {
|
|
1144
|
+
return Math.max(0, Math.ceil((prompt.expiresAt - Date.now()) / 1e3));
|
|
1145
|
+
}
|
|
1146
|
+
if (typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0) {
|
|
1147
|
+
return Math.floor(prompt.expirySeconds);
|
|
1148
|
+
}
|
|
1149
|
+
return void 0;
|
|
1150
|
+
}
|
|
1151
|
+
function isUserActionExpired(prompt) {
|
|
1152
|
+
const left = getUserActionSecondsLeft(prompt);
|
|
1153
|
+
return left !== void 0 && left <= 0;
|
|
1154
|
+
}
|
|
1155
|
+
var EMPTY_USER_ACTION_STATE2 = { prompts: [], notifications: [] };
|
|
1183
1156
|
function upsertPrompt(prompts, req) {
|
|
1184
|
-
const active = { ...req, status: "pending" };
|
|
1185
1157
|
const idx = prompts.findIndex(
|
|
1186
1158
|
(p) => req.toolCallId ? p.toolCallId === req.toolCallId : p.userActionId === req.userActionId
|
|
1187
1159
|
);
|
|
1160
|
+
const expiresAt = resolveUserActionExpiresAt(req, idx >= 0 ? prompts[idx] : void 0);
|
|
1161
|
+
const active = { ...req, status: "pending", expiresAt };
|
|
1188
1162
|
if (idx >= 0) {
|
|
1189
1163
|
const next = prompts.slice();
|
|
1190
1164
|
next[idx] = active;
|
|
@@ -1207,32 +1181,12 @@ function getStoredOrInitialMessages(config) {
|
|
|
1207
1181
|
function getSessionIdFromMessages(messages) {
|
|
1208
1182
|
return messages.find((message) => message.sessionId)?.sessionId;
|
|
1209
1183
|
}
|
|
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
1184
|
function useChatV2(config, callbacks = {}) {
|
|
1230
1185
|
const [messages, setMessages] = react.useState(() => getStoredOrInitialMessages(config));
|
|
1231
1186
|
const [isWaitingForResponse, setIsWaitingForResponse] = react.useState(() => {
|
|
1232
1187
|
if (!config.userId) return false;
|
|
1233
1188
|
return activeStreamStore.get(config.userId)?.isWaiting ?? false;
|
|
1234
1189
|
});
|
|
1235
|
-
const [isUploadingAttachments, setIsUploadingAttachments] = react.useState(false);
|
|
1236
1190
|
const sessionIdRef = react.useRef(
|
|
1237
1191
|
getSessionIdFromMessages(getStoredOrInitialMessages(config)) ?? config.initialSessionId ?? void 0
|
|
1238
1192
|
);
|
|
@@ -1275,7 +1229,28 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1275
1229
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1276
1230
|
[]
|
|
1277
1231
|
);
|
|
1278
|
-
const [userActionState, setUserActionState] = react.useState(
|
|
1232
|
+
const [userActionState, setUserActionState] = react.useState(() => {
|
|
1233
|
+
if (!config.userId) return EMPTY_USER_ACTION_STATE2;
|
|
1234
|
+
return activeStreamStore.get(config.userId)?.userActionState ?? EMPTY_USER_ACTION_STATE2;
|
|
1235
|
+
});
|
|
1236
|
+
const storeAwareSetUserActionState = react.useCallback(
|
|
1237
|
+
(updater) => {
|
|
1238
|
+
const streamUserId = streamUserIdRef.current;
|
|
1239
|
+
const currentUserId = configRef.current.userId;
|
|
1240
|
+
const storeKey = streamUserId ?? currentUserId;
|
|
1241
|
+
if (storeKey && activeStreamStore.has(storeKey)) {
|
|
1242
|
+
activeStreamStore.applyUserActionState(
|
|
1243
|
+
storeKey,
|
|
1244
|
+
updater
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
if (!streamUserId || streamUserId === currentUserId) {
|
|
1248
|
+
setUserActionState(updater);
|
|
1249
|
+
}
|
|
1250
|
+
},
|
|
1251
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1252
|
+
[]
|
|
1253
|
+
);
|
|
1279
1254
|
const wrappedCallbacks = react.useMemo(() => ({
|
|
1280
1255
|
...callbacksRef.current,
|
|
1281
1256
|
onMessageSent: (message) => callbacksRef.current.onMessageSent?.(message),
|
|
@@ -1285,14 +1260,14 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1285
1260
|
onExecutionTraceClick: (data) => callbacksRef.current.onExecutionTraceClick?.(data),
|
|
1286
1261
|
onSessionIdChange: (sessionId) => callbacksRef.current.onSessionIdChange?.(sessionId),
|
|
1287
1262
|
onUserActionRequired: (request) => {
|
|
1288
|
-
|
|
1263
|
+
storeAwareSetUserActionState((prev) => ({
|
|
1289
1264
|
...prev,
|
|
1290
1265
|
prompts: upsertPrompt(prev.prompts, request)
|
|
1291
1266
|
}));
|
|
1292
1267
|
callbacksRef.current.onUserActionRequired?.(request);
|
|
1293
1268
|
},
|
|
1294
1269
|
onUserNotification: (notification) => {
|
|
1295
|
-
|
|
1270
|
+
storeAwareSetUserActionState(
|
|
1296
1271
|
(prev) => prev.notifications.some((n) => n.id === notification.id) ? prev : { ...prev, notifications: [...prev.notifications, notification] }
|
|
1297
1272
|
);
|
|
1298
1273
|
callbacksRef.current.onUserNotification?.(notification);
|
|
@@ -1307,45 +1282,21 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1307
1282
|
);
|
|
1308
1283
|
const sendMessage = react.useCallback(
|
|
1309
1284
|
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
|
-
}
|
|
1285
|
+
if (!userMessage.trim()) return;
|
|
1328
1286
|
if (!sessionIdRef.current && configRef.current.autoGenerateSessionId !== false) {
|
|
1329
1287
|
sessionIdRef.current = generateId();
|
|
1330
1288
|
callbacksRef.current.onSessionIdChange?.(sessionIdRef.current);
|
|
1331
1289
|
}
|
|
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
1290
|
const userMessageId = `user-${Date.now()}`;
|
|
1339
1291
|
const userMsg = {
|
|
1340
1292
|
id: userMessageId,
|
|
1341
1293
|
sessionId: sessionIdRef.current,
|
|
1342
1294
|
role: "user",
|
|
1343
|
-
content:
|
|
1344
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1345
|
-
attachments: messageAttachments
|
|
1295
|
+
content: userMessage,
|
|
1296
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1346
1297
|
};
|
|
1347
1298
|
setMessages((prev) => [...prev, userMsg]);
|
|
1348
|
-
callbacksRef.current.onMessageSent?.(
|
|
1299
|
+
callbacksRef.current.onMessageSent?.(userMessage);
|
|
1349
1300
|
setIsWaitingForResponse(true);
|
|
1350
1301
|
callbacksRef.current.onStreamStart?.();
|
|
1351
1302
|
const streamingId = `assistant-${Date.now()}`;
|
|
@@ -1372,14 +1323,11 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1372
1323
|
activeStreamStore.start(userId, abortController, initialMessages);
|
|
1373
1324
|
}
|
|
1374
1325
|
const newSessionId = await startStream(
|
|
1375
|
-
|
|
1326
|
+
userMessage,
|
|
1376
1327
|
streamingId,
|
|
1377
1328
|
sessionIdRef.current,
|
|
1378
1329
|
abortController,
|
|
1379
|
-
|
|
1380
|
-
analysisMode: options?.analysisMode,
|
|
1381
|
-
attachments: streamAttachments
|
|
1382
|
-
}
|
|
1330
|
+
options
|
|
1383
1331
|
);
|
|
1384
1332
|
const finalStreamUserId = streamUserIdRef.current ?? userId;
|
|
1385
1333
|
if (finalStreamUserId) {
|
|
@@ -1409,7 +1357,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1409
1357
|
streamUserIdRef.current = void 0;
|
|
1410
1358
|
cancelStreamManager();
|
|
1411
1359
|
setIsWaitingForResponse(false);
|
|
1412
|
-
|
|
1360
|
+
storeAwareSetUserActionState((prev) => ({ ...prev, prompts: [] }));
|
|
1413
1361
|
setMessages(
|
|
1414
1362
|
(prev) => prev.map((msg) => {
|
|
1415
1363
|
if (msg.isStreaming) {
|
|
@@ -1424,7 +1372,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1424
1372
|
return msg;
|
|
1425
1373
|
})
|
|
1426
1374
|
);
|
|
1427
|
-
}, [cancelStreamManager]);
|
|
1375
|
+
}, [cancelStreamManager, storeAwareSetUserActionState]);
|
|
1428
1376
|
const resetSession = react.useCallback(() => {
|
|
1429
1377
|
const streamUserId = streamUserIdRef.current ?? configRef.current.userId;
|
|
1430
1378
|
if (streamUserId) {
|
|
@@ -1438,7 +1386,7 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1438
1386
|
sessionIdRef.current = void 0;
|
|
1439
1387
|
abortControllerRef.current?.abort();
|
|
1440
1388
|
setIsWaitingForResponse(false);
|
|
1441
|
-
|
|
1389
|
+
storeAwareSetUserActionState(EMPTY_USER_ACTION_STATE2);
|
|
1442
1390
|
}, []);
|
|
1443
1391
|
const getSessionId = react.useCallback(() => {
|
|
1444
1392
|
return sessionIdRef.current;
|
|
@@ -1448,21 +1396,21 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1448
1396
|
}, [messages]);
|
|
1449
1397
|
const setPromptStatus = react.useCallback(
|
|
1450
1398
|
(userActionId, status) => {
|
|
1451
|
-
|
|
1399
|
+
storeAwareSetUserActionState((prev) => ({
|
|
1452
1400
|
...prev,
|
|
1453
1401
|
prompts: prev.prompts.map(
|
|
1454
1402
|
(p) => p.userActionId === userActionId ? { ...p, status } : p
|
|
1455
1403
|
)
|
|
1456
1404
|
}));
|
|
1457
1405
|
},
|
|
1458
|
-
[]
|
|
1406
|
+
[storeAwareSetUserActionState]
|
|
1459
1407
|
);
|
|
1460
1408
|
const removePrompt = react.useCallback((userActionId) => {
|
|
1461
|
-
|
|
1409
|
+
storeAwareSetUserActionState((prev) => ({
|
|
1462
1410
|
...prev,
|
|
1463
1411
|
prompts: prev.prompts.filter((p) => p.userActionId !== userActionId)
|
|
1464
1412
|
}));
|
|
1465
|
-
}, []);
|
|
1413
|
+
}, [storeAwareSetUserActionState]);
|
|
1466
1414
|
const submitUserAction2 = react.useCallback(
|
|
1467
1415
|
async (userActionId, content) => {
|
|
1468
1416
|
setPromptStatus(userActionId, "submitting");
|
|
@@ -1517,12 +1465,25 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1517
1465
|
},
|
|
1518
1466
|
[setPromptStatus]
|
|
1519
1467
|
);
|
|
1468
|
+
const expireUserAction2 = react.useCallback(
|
|
1469
|
+
async (userActionId) => {
|
|
1470
|
+
setPromptStatus(userActionId, "expired");
|
|
1471
|
+
try {
|
|
1472
|
+
await expireUserAction(configRef.current, userActionId);
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
if (error instanceof UserActionStaleError) return;
|
|
1475
|
+
callbacksRef.current.onError?.(error);
|
|
1476
|
+
throw error;
|
|
1477
|
+
}
|
|
1478
|
+
},
|
|
1479
|
+
[setPromptStatus]
|
|
1480
|
+
);
|
|
1520
1481
|
const dismissNotification = react.useCallback((id) => {
|
|
1521
|
-
|
|
1482
|
+
storeAwareSetUserActionState((prev) => ({
|
|
1522
1483
|
...prev,
|
|
1523
1484
|
notifications: prev.notifications.filter((n) => n.id !== id)
|
|
1524
1485
|
}));
|
|
1525
|
-
}, []);
|
|
1486
|
+
}, [storeAwareSetUserActionState]);
|
|
1526
1487
|
react.useEffect(() => {
|
|
1527
1488
|
const prevSubscriptionUserId = subscriptionPrevUserIdRef.current;
|
|
1528
1489
|
subscriptionPrevUserIdRef.current = config.userId;
|
|
@@ -1531,14 +1492,17 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1531
1492
|
if (prevSubscriptionUserId && prevSubscriptionUserId !== userId && streamUserIdRef.current === prevSubscriptionUserId && !activeStreamStore.has(prevSubscriptionUserId) && activeStreamStore.has(userId)) {
|
|
1532
1493
|
streamUserIdRef.current = userId;
|
|
1533
1494
|
}
|
|
1534
|
-
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting) => {
|
|
1495
|
+
const unsubscribe = activeStreamStore.subscribe(userId, (msgs, isWaiting, actions) => {
|
|
1535
1496
|
setMessages(msgs);
|
|
1536
1497
|
setIsWaitingForResponse(isWaiting);
|
|
1498
|
+
setUserActionState(actions);
|
|
1537
1499
|
});
|
|
1538
1500
|
const active = activeStreamStore.get(userId);
|
|
1539
1501
|
if (active) {
|
|
1502
|
+
streamUserIdRef.current = userId;
|
|
1540
1503
|
setMessages(active.messages);
|
|
1541
1504
|
setIsWaitingForResponse(active.isWaiting);
|
|
1505
|
+
setUserActionState(active.userActionState);
|
|
1542
1506
|
}
|
|
1543
1507
|
return unsubscribe;
|
|
1544
1508
|
}, [config.userId]);
|
|
@@ -1557,18 +1521,6 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1557
1521
|
setMessages(config.initialMessages);
|
|
1558
1522
|
sessionIdRef.current = getSessionIdFromMessages(config.initialMessages) ?? config.initialSessionId;
|
|
1559
1523
|
}, [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
1524
|
react.useEffect(() => {
|
|
1573
1525
|
const prevUserId = prevUserIdRef.current;
|
|
1574
1526
|
prevUserIdRef.current = config.userId;
|
|
@@ -1578,12 +1530,14 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1578
1530
|
setMessages([]);
|
|
1579
1531
|
sessionIdRef.current = void 0;
|
|
1580
1532
|
setIsWaitingForResponse(false);
|
|
1581
|
-
setUserActionState(
|
|
1533
|
+
setUserActionState(EMPTY_USER_ACTION_STATE2);
|
|
1582
1534
|
} else if (config.userId) {
|
|
1583
1535
|
const active = activeStreamStore.get(config.userId);
|
|
1584
1536
|
if (active) {
|
|
1537
|
+
streamUserIdRef.current = config.userId;
|
|
1585
1538
|
setMessages(active.messages);
|
|
1586
1539
|
setIsWaitingForResponse(active.isWaiting);
|
|
1540
|
+
setUserActionState(active.userActionState);
|
|
1587
1541
|
sessionIdRef.current = getSessionIdFromMessages(active.messages) ?? config.initialSessionId;
|
|
1588
1542
|
return;
|
|
1589
1543
|
}
|
|
@@ -1603,12 +1557,12 @@ function useChatV2(config, callbacks = {}) {
|
|
|
1603
1557
|
getSessionId,
|
|
1604
1558
|
getMessages,
|
|
1605
1559
|
isWaitingForResponse,
|
|
1606
|
-
isUploadingAttachments,
|
|
1607
1560
|
sessionId: sessionIdRef.current,
|
|
1608
1561
|
userActionState,
|
|
1609
1562
|
submitUserAction: submitUserAction2,
|
|
1610
1563
|
cancelUserAction: cancelUserAction2,
|
|
1611
1564
|
resendUserAction: resendUserAction2,
|
|
1565
|
+
expireUserAction: expireUserAction2,
|
|
1612
1566
|
dismissNotification
|
|
1613
1567
|
};
|
|
1614
1568
|
}
|
|
@@ -2571,35 +2525,37 @@ function uaPalette(isDark) {
|
|
|
2571
2525
|
grabber: "rgba(15,23,42,0.18)"
|
|
2572
2526
|
};
|
|
2573
2527
|
}
|
|
2574
|
-
function promptInitialSeconds(prompt) {
|
|
2575
|
-
return prompt && typeof prompt.expirySeconds === "number" && prompt.expirySeconds > 0 ? Math.floor(prompt.expirySeconds) : void 0;
|
|
2576
|
-
}
|
|
2577
2528
|
function promptTimerKey(prompt) {
|
|
2578
|
-
return prompt ? `${prompt.userActionId}|${prompt.subAction ?? ""}` : "";
|
|
2529
|
+
return prompt ? `${prompt.userActionId}|${prompt.subAction ?? ""}|${prompt.expiresAt ?? ""}` : "";
|
|
2579
2530
|
}
|
|
2580
2531
|
function useExpiredFlag(prompt) {
|
|
2581
|
-
const initial = promptInitialSeconds(prompt);
|
|
2582
2532
|
const key = promptTimerKey(prompt);
|
|
2583
|
-
const [expired, setExpired] = react.useState(false);
|
|
2533
|
+
const [expired, setExpired] = react.useState(() => prompt ? isUserActionExpired(prompt) : false);
|
|
2584
2534
|
react.useEffect(() => {
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2535
|
+
const secondsLeft = prompt ? getUserActionSecondsLeft(prompt) : void 0;
|
|
2536
|
+
setExpired(secondsLeft !== void 0 && secondsLeft <= 0);
|
|
2537
|
+
if (secondsLeft === void 0 || secondsLeft <= 0) return;
|
|
2538
|
+
const t = setTimeout(() => setExpired(true), secondsLeft * 1e3);
|
|
2588
2539
|
return () => clearTimeout(t);
|
|
2589
|
-
}, [key,
|
|
2540
|
+
}, [key, prompt]);
|
|
2590
2541
|
return expired;
|
|
2591
2542
|
}
|
|
2592
|
-
function useSecondsLeft(
|
|
2593
|
-
const
|
|
2594
|
-
const [left, setLeft] = react.useState(
|
|
2543
|
+
function useSecondsLeft(prompt, restartKey) {
|
|
2544
|
+
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;
|
|
2545
|
+
const [left, setLeft] = react.useState(
|
|
2546
|
+
() => deadlineMs !== void 0 ? Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3)) : void 0
|
|
2547
|
+
);
|
|
2595
2548
|
react.useEffect(() => {
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2549
|
+
if (deadlineMs === void 0) {
|
|
2550
|
+
setLeft(void 0);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
const remaining = () => Math.max(0, Math.ceil((deadlineMs - Date.now()) / 1e3));
|
|
2554
|
+
const sync = () => setLeft(remaining());
|
|
2555
|
+
sync();
|
|
2556
|
+
const id = setInterval(sync, 1e3);
|
|
2601
2557
|
return () => clearInterval(id);
|
|
2602
|
-
}, [restartKey,
|
|
2558
|
+
}, [restartKey, deadlineMs]);
|
|
2603
2559
|
return left;
|
|
2604
2560
|
}
|
|
2605
2561
|
function SheetTimerPill({
|
|
@@ -2607,7 +2563,7 @@ function SheetTimerPill({
|
|
|
2607
2563
|
accent,
|
|
2608
2564
|
isDark
|
|
2609
2565
|
}) {
|
|
2610
|
-
const left = useSecondsLeft(prompt
|
|
2566
|
+
const left = useSecondsLeft(prompt, promptTimerKey(prompt));
|
|
2611
2567
|
if (left === void 0 || prompt.status === "stale") return null;
|
|
2612
2568
|
const expired = left <= 0;
|
|
2613
2569
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2629,7 +2585,7 @@ function CardTimerPill({
|
|
|
2629
2585
|
accent,
|
|
2630
2586
|
isDark
|
|
2631
2587
|
}) {
|
|
2632
|
-
const left = useSecondsLeft(prompt
|
|
2588
|
+
const left = useSecondsLeft(prompt, promptTimerKey(prompt));
|
|
2633
2589
|
if (left === void 0 || left <= 0) return null;
|
|
2634
2590
|
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
2591
|
left,
|
|
@@ -2695,6 +2651,7 @@ function VerificationBody({
|
|
|
2695
2651
|
expired,
|
|
2696
2652
|
pal,
|
|
2697
2653
|
accent,
|
|
2654
|
+
isDark,
|
|
2698
2655
|
onSubmit,
|
|
2699
2656
|
onCancel,
|
|
2700
2657
|
onResend
|
|
@@ -2757,7 +2714,11 @@ function VerificationBody({
|
|
|
2757
2714
|
} catch {
|
|
2758
2715
|
}
|
|
2759
2716
|
}, [locked, onResend, prompt.userActionId, resendSec]);
|
|
2760
|
-
const
|
|
2717
|
+
const promptMessage = prompt.message?.trim();
|
|
2718
|
+
const description = promptMessage || (isNumeric ? `Enter the ${codeLen}-digit code to continue` : "Enter the verification code to continue");
|
|
2719
|
+
const renderMarkdown = !!promptMessage && prompt.metadata?.["payman/messageFormat"] === "markdown";
|
|
2720
|
+
const mdStyles = isDark ? MD_STYLES_DARK : MD_STYLES_LIGHT;
|
|
2721
|
+
const mdRules = react.useMemo(() => makeMdRules(isDark, accent), [isDark, accent]);
|
|
2761
2722
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: ub.bodyWrap, children: [
|
|
2762
2723
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2763
2724
|
reactNative.ScrollView,
|
|
@@ -2767,7 +2728,7 @@ function VerificationBody({
|
|
|
2767
2728
|
keyboardShouldPersistTaps: "handled",
|
|
2768
2729
|
showsVerticalScrollIndicator: false,
|
|
2769
2730
|
children: [
|
|
2770
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.desc, { color: pal.muted }], children: description }),
|
|
2731
|
+
renderMarkdown ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: ub.messageBlock, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown__default.default, { style: mdStyles, rules: mdRules, children: description.replace(/\\n/g, "\n") }) }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [ub.desc, { color: pal.muted }], children: description }),
|
|
2771
2732
|
isNumeric ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2772
2733
|
CodeInput,
|
|
2773
2734
|
{
|
|
@@ -3106,6 +3067,7 @@ function UserActionSheet({
|
|
|
3106
3067
|
expired,
|
|
3107
3068
|
pal,
|
|
3108
3069
|
accent,
|
|
3070
|
+
isDark,
|
|
3109
3071
|
onSubmit,
|
|
3110
3072
|
onCancel,
|
|
3111
3073
|
onResend
|