@sagepilot-ai/react-native-sdk 0.2.5 → 0.3.0
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/README.md +139 -32
- package/android/build.gradle +13 -4
- package/android/src/main/java/ai/sagepilot/reactnativesdk/SagepilotReactNativeSdkPackage.java +2 -0
- package/dist/index.d.mts +25 -12
- package/dist/index.d.ts +25 -12
- package/dist/index.js +604 -138
- package/dist/index.mjs +573 -112
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -30,6 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION: () => SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION,
|
|
34
|
+
SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES: () => SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES,
|
|
35
|
+
SAGEPILOT_DEFAULT_IMAGE_QUALITY: () => SAGEPILOT_DEFAULT_IMAGE_QUALITY,
|
|
33
36
|
SagepilotChat: () => SagepilotChat,
|
|
34
37
|
SagepilotChatError: () => SagepilotChatError,
|
|
35
38
|
SagepilotChatProvider: () => SagepilotChatProvider,
|
|
@@ -38,10 +41,15 @@ __export(index_exports, {
|
|
|
38
41
|
createKeychainTokenStorage: () => createKeychainTokenStorage,
|
|
39
42
|
createSagepilotFilePicker: () => createSagepilotFilePicker,
|
|
40
43
|
createSagepilotFileStore: () => createSagepilotFileStore,
|
|
44
|
+
ensureSagepilotAndroidCameraPermission: () => ensureSagepilotAndroidCameraPermission,
|
|
45
|
+
promptOpenSagepilotCameraSettings: () => promptOpenSagepilotCameraSettings,
|
|
41
46
|
useSagepilotChat: () => useSagepilotChat
|
|
42
47
|
});
|
|
43
48
|
module.exports = __toCommonJS(index_exports);
|
|
44
49
|
|
|
50
|
+
// src/public/SdkClient.ts
|
|
51
|
+
var import_react_native = require("react-native");
|
|
52
|
+
|
|
45
53
|
// src/core/errors/SagepilotChatError.ts
|
|
46
54
|
var SagepilotChatError = class extends Error {
|
|
47
55
|
constructor(code, message, options = {}) {
|
|
@@ -59,7 +67,7 @@ var SagepilotChatError = class extends Error {
|
|
|
59
67
|
|
|
60
68
|
// src/core/config/constants.ts
|
|
61
69
|
var SDK_NAME = "@sagepilot-ai/react-native-sdk";
|
|
62
|
-
var SDK_VERSION = "0.
|
|
70
|
+
var SDK_VERSION = "0.3.0";
|
|
63
71
|
var DEFAULT_HOST = "https://app.sagepilot.ai";
|
|
64
72
|
var DEFAULT_WIDGET_HOST = "https://app.sagepilot.ai";
|
|
65
73
|
var CUSTOMER_API_PREFIX = "/customer-api/v1";
|
|
@@ -368,6 +376,34 @@ async function resolveDeviceInfo(adapter) {
|
|
|
368
376
|
return adapter.getDeviceInfo();
|
|
369
377
|
}
|
|
370
378
|
|
|
379
|
+
// src/core/storage/cache.ts
|
|
380
|
+
function createAsyncStorageCacheStorage(asyncStorage) {
|
|
381
|
+
return {
|
|
382
|
+
getItem: (key) => asyncStorage.getItem(key),
|
|
383
|
+
setItem: (key, value) => asyncStorage.setItem(key, value),
|
|
384
|
+
removeItem: (key) => asyncStorage.removeItem(key)
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function createJsonCache(storage, namespace) {
|
|
388
|
+
return {
|
|
389
|
+
async get(key) {
|
|
390
|
+
const value = await storage.getItem(`${namespace}:${key}`);
|
|
391
|
+
if (!value) return null;
|
|
392
|
+
try {
|
|
393
|
+
return JSON.parse(value);
|
|
394
|
+
} catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
async set(key, value) {
|
|
399
|
+
await storage.setItem(`${namespace}:${key}`, JSON.stringify(value));
|
|
400
|
+
},
|
|
401
|
+
async remove(key) {
|
|
402
|
+
await storage.removeItem(`${namespace}:${key}`);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
371
407
|
// src/resources/channels.ts
|
|
372
408
|
function bootstrapChannel(http, host, channelId) {
|
|
373
409
|
return http.request(
|
|
@@ -451,6 +487,9 @@ function fetchUnreadCount(http, host, channelId, sessionId, authorizationHeader)
|
|
|
451
487
|
}
|
|
452
488
|
|
|
453
489
|
// src/public/SdkClient.ts
|
|
490
|
+
var PRESENTATION_CACHE_NAMESPACE = "sagepilot_rn_presentation";
|
|
491
|
+
var PRESENTATION_CACHE_KEY = "state";
|
|
492
|
+
var PRESENTATION_RESTORE_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
454
493
|
var DEFAULT_MOBILE_LAUNCHER_CONFIG = {
|
|
455
494
|
label: "Chat",
|
|
456
495
|
buttonColor: "#173c2d",
|
|
@@ -520,6 +559,10 @@ function readRecordField(input, key) {
|
|
|
520
559
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
521
560
|
return value;
|
|
522
561
|
}
|
|
562
|
+
function isFreshPresentationState(updatedAt) {
|
|
563
|
+
if (typeof updatedAt !== "number" || !Number.isFinite(updatedAt)) return false;
|
|
564
|
+
return Date.now() - updatedAt <= PRESENTATION_RESTORE_MAX_AGE_MS;
|
|
565
|
+
}
|
|
523
566
|
function toPublicSessionState(session) {
|
|
524
567
|
return {
|
|
525
568
|
session_id: session.session_id,
|
|
@@ -609,6 +652,7 @@ var SagepilotReactNativeChat = class {
|
|
|
609
652
|
this.channel = channel;
|
|
610
653
|
this.session = session;
|
|
611
654
|
this.isConfigured = true;
|
|
655
|
+
await this.restorePresentationFromCache();
|
|
612
656
|
this.emitReady();
|
|
613
657
|
this.emitState();
|
|
614
658
|
if (config.behavior?.enableUnreadPolling ?? true) {
|
|
@@ -748,12 +792,14 @@ var SagepilotReactNativeChat = class {
|
|
|
748
792
|
this.hostedChatView = { screen: "home" };
|
|
749
793
|
if (this.presented) {
|
|
750
794
|
this.emitState();
|
|
795
|
+
this.persistPresentationState();
|
|
751
796
|
return true;
|
|
752
797
|
}
|
|
753
798
|
this.presented = true;
|
|
754
799
|
this.setUnreadCount(0);
|
|
755
800
|
this.presentCallbacks.forEach((callback) => callback(this.getLifecycleState()));
|
|
756
801
|
this.emitState();
|
|
802
|
+
this.persistPresentationState();
|
|
757
803
|
return true;
|
|
758
804
|
}
|
|
759
805
|
presentMessages() {
|
|
@@ -781,6 +827,7 @@ var SagepilotReactNativeChat = class {
|
|
|
781
827
|
this.emitUnreadChange();
|
|
782
828
|
this.dismissCallbacks.forEach((callback) => callback(this.getLifecycleState()));
|
|
783
829
|
this.emitState();
|
|
830
|
+
this.persistPresentationState();
|
|
784
831
|
return true;
|
|
785
832
|
}
|
|
786
833
|
hide() {
|
|
@@ -915,6 +962,10 @@ var SagepilotReactNativeChat = class {
|
|
|
915
962
|
}
|
|
916
963
|
return this.session?.conversation_id ?? void 0;
|
|
917
964
|
}
|
|
965
|
+
/** Returns the chat id that is explicitly part of the current hosted URL. */
|
|
966
|
+
getHostedRouteChatId() {
|
|
967
|
+
return this.hostedChatView.screen === "composer" ? this.hostedChatView.chatId : void 0;
|
|
968
|
+
}
|
|
918
969
|
getHostedIdentityMessage() {
|
|
919
970
|
if (!this.legacyWidgetJwt) return null;
|
|
920
971
|
return {
|
|
@@ -925,6 +976,34 @@ var SagepilotReactNativeChat = class {
|
|
|
925
976
|
}
|
|
926
977
|
};
|
|
927
978
|
}
|
|
979
|
+
/** Captures the hosted route that should receive a picked attachment batch. */
|
|
980
|
+
getHostedAttachmentTarget() {
|
|
981
|
+
return {
|
|
982
|
+
chatId: this.getActiveHostedChatId(),
|
|
983
|
+
routeChatId: this.getHostedRouteChatId(),
|
|
984
|
+
hostedChatView: this.serializeHostedChatView(this.hostedChatView)
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
/** Restores the hosted route for a queued attachment batch before delivery. */
|
|
988
|
+
restoreHostedAttachmentTarget(target) {
|
|
989
|
+
if (import_react_native.Platform.OS !== "android") return false;
|
|
990
|
+
if (!target) return false;
|
|
991
|
+
const hostedChatView = this.readPersistedHostedChatView(target.hostedChatView);
|
|
992
|
+
if (!hostedChatView) return false;
|
|
993
|
+
const currentChatId = this.getActiveHostedChatId();
|
|
994
|
+
const targetChatId = normalizeOptionalString(target.chatId);
|
|
995
|
+
const routeChatId = normalizeOptionalString(target.routeChatId) ?? (hostedChatView.screen === "composer" ? normalizeOptionalString(hostedChatView.chatId) : void 0);
|
|
996
|
+
const nextHostedChatView = routeChatId && hostedChatView.screen === "composer" ? this.withPinnedChatId(hostedChatView, routeChatId) : hostedChatView;
|
|
997
|
+
if (this.presented && (!targetChatId || currentChatId === targetChatId) && this.areHostedChatViewsEqual(this.hostedChatView, nextHostedChatView)) {
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
this.hostedChatView = nextHostedChatView;
|
|
1001
|
+
this.presented = true;
|
|
1002
|
+
this.setUnreadCount(0);
|
|
1003
|
+
this.emitState();
|
|
1004
|
+
this.persistPresentationState();
|
|
1005
|
+
return true;
|
|
1006
|
+
}
|
|
928
1007
|
handleHostedBridgeMessage(message) {
|
|
929
1008
|
if (!message) return false;
|
|
930
1009
|
if (message.type === "close_widget") {
|
|
@@ -992,6 +1071,7 @@ var SagepilotReactNativeChat = class {
|
|
|
992
1071
|
}
|
|
993
1072
|
destroy() {
|
|
994
1073
|
this.stopUnreadPolling();
|
|
1074
|
+
this.persistPresentationState({ cleanShutdown: true, presented: false });
|
|
995
1075
|
this.resetRuntimeState();
|
|
996
1076
|
this.emitState();
|
|
997
1077
|
this.identifyCallbacks.clear();
|
|
@@ -1147,8 +1227,86 @@ var SagepilotReactNativeChat = class {
|
|
|
1147
1227
|
this.presentCallbacks.forEach((callback) => callback(this.getLifecycleState()));
|
|
1148
1228
|
}
|
|
1149
1229
|
this.emitState();
|
|
1230
|
+
this.persistPresentationState();
|
|
1150
1231
|
return true;
|
|
1151
1232
|
}
|
|
1233
|
+
/** Returns the dedicated cache used for hosted-chat presentation recovery. */
|
|
1234
|
+
getPresentationCache() {
|
|
1235
|
+
const cacheStorage = this.runtimeOptions?.cacheStorage;
|
|
1236
|
+
if (!cacheStorage) return null;
|
|
1237
|
+
return createJsonCache(cacheStorage, PRESENTATION_CACHE_NAMESPACE);
|
|
1238
|
+
}
|
|
1239
|
+
/** Removes callback-only fields so the hosted route can be safely serialized. */
|
|
1240
|
+
serializeHostedChatView(view) {
|
|
1241
|
+
if (view.screen !== "composer") return view;
|
|
1242
|
+
return {
|
|
1243
|
+
screen: "composer",
|
|
1244
|
+
message: view.message,
|
|
1245
|
+
mode: view.mode,
|
|
1246
|
+
chatId: view.chatId,
|
|
1247
|
+
metadata: view.metadata
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
/** Validates a persisted hosted view before using it to rebuild a widget URL. */
|
|
1251
|
+
readPersistedHostedChatView(view) {
|
|
1252
|
+
if (!view || typeof view !== "object") return null;
|
|
1253
|
+
const record = view;
|
|
1254
|
+
if (record.screen === "home") return { screen: "home" };
|
|
1255
|
+
if (record.screen === "messages") return { screen: "messages" };
|
|
1256
|
+
if (record.screen !== "composer") return null;
|
|
1257
|
+
const mode = record.mode === "new" ? "new" : "auto";
|
|
1258
|
+
return {
|
|
1259
|
+
screen: "composer",
|
|
1260
|
+
message: typeof record.message === "string" ? record.message : void 0,
|
|
1261
|
+
mode,
|
|
1262
|
+
chatId: typeof record.chatId === "string" && record.chatId ? record.chatId : void 0,
|
|
1263
|
+
metadata: readRecordField(record, "metadata")
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
/** Forces a persisted hosted route to target the exact chat that owns a pending attachment. */
|
|
1267
|
+
withPinnedChatId(view, chatId) {
|
|
1268
|
+
if (view.screen === "composer") {
|
|
1269
|
+
return {
|
|
1270
|
+
...view,
|
|
1271
|
+
chatId
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
return {
|
|
1275
|
+
screen: "composer",
|
|
1276
|
+
mode: "auto",
|
|
1277
|
+
chatId
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
/** Compares hosted route state without relying on object identity. */
|
|
1281
|
+
areHostedChatViewsEqual(first, second) {
|
|
1282
|
+
return JSON.stringify(this.serializeHostedChatView(first)) === JSON.stringify(this.serializeHostedChatView(second));
|
|
1283
|
+
}
|
|
1284
|
+
/** Persists presentation state without blocking customer-facing SDK methods. */
|
|
1285
|
+
persistPresentationState(options) {
|
|
1286
|
+
const cache = this.getPresentationCache();
|
|
1287
|
+
if (!cache) return;
|
|
1288
|
+
const state = {
|
|
1289
|
+
presented: options?.presented ?? this.presented,
|
|
1290
|
+
hostedChatView: this.serializeHostedChatView(this.hostedChatView),
|
|
1291
|
+
updatedAt: Date.now(),
|
|
1292
|
+
cleanShutdown: options?.cleanShutdown
|
|
1293
|
+
};
|
|
1294
|
+
void cache.set(PRESENTATION_CACHE_KEY, state).catch(() => void 0);
|
|
1295
|
+
}
|
|
1296
|
+
/** Restores Android presentation state after configure without resetting the hosted route to Home. */
|
|
1297
|
+
async restorePresentationFromCache() {
|
|
1298
|
+
const cache = this.getPresentationCache();
|
|
1299
|
+
if (!cache || import_react_native.Platform.OS !== "android") return;
|
|
1300
|
+
const persisted = await cache.get(PRESENTATION_CACHE_KEY).catch(() => null);
|
|
1301
|
+
if (persisted?.cleanShutdown) return;
|
|
1302
|
+
if (!isFreshPresentationState(persisted?.updatedAt)) return;
|
|
1303
|
+
if (!persisted?.presented) return;
|
|
1304
|
+
const hostedChatView = this.readPersistedHostedChatView(persisted.hostedChatView);
|
|
1305
|
+
if (!hostedChatView) return;
|
|
1306
|
+
this.hostedChatView = hostedChatView;
|
|
1307
|
+
this.presented = true;
|
|
1308
|
+
this.setUnreadCount(0);
|
|
1309
|
+
}
|
|
1152
1310
|
};
|
|
1153
1311
|
var internalSagepilotChat = new SagepilotReactNativeChat();
|
|
1154
1312
|
var SagepilotChat = {
|
|
@@ -1185,34 +1343,6 @@ var SagepilotChat = {
|
|
|
1185
1343
|
destroy: () => internalSagepilotChat.destroy()
|
|
1186
1344
|
};
|
|
1187
1345
|
|
|
1188
|
-
// src/core/storage/cache.ts
|
|
1189
|
-
function createAsyncStorageCacheStorage(asyncStorage) {
|
|
1190
|
-
return {
|
|
1191
|
-
getItem: (key) => asyncStorage.getItem(key),
|
|
1192
|
-
setItem: (key, value) => asyncStorage.setItem(key, value),
|
|
1193
|
-
removeItem: (key) => asyncStorage.removeItem(key)
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
function createJsonCache(storage, namespace) {
|
|
1197
|
-
return {
|
|
1198
|
-
async get(key) {
|
|
1199
|
-
const value = await storage.getItem(`${namespace}:${key}`);
|
|
1200
|
-
if (!value) return null;
|
|
1201
|
-
try {
|
|
1202
|
-
return JSON.parse(value);
|
|
1203
|
-
} catch {
|
|
1204
|
-
return null;
|
|
1205
|
-
}
|
|
1206
|
-
},
|
|
1207
|
-
async set(key, value) {
|
|
1208
|
-
await storage.setItem(`${namespace}:${key}`, JSON.stringify(value));
|
|
1209
|
-
},
|
|
1210
|
-
async remove(key) {
|
|
1211
|
-
await storage.removeItem(`${namespace}:${key}`);
|
|
1212
|
-
}
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
1346
|
// src/core/storage/fileStore.ts
|
|
1217
1347
|
var DEFAULT_DIRECTORY_NAME = "sagepilot-attachments";
|
|
1218
1348
|
function sanitizeKey(key) {
|
|
@@ -1270,21 +1400,30 @@ function createSagepilotFileStore(blobUtil, options = {}) {
|
|
|
1270
1400
|
}
|
|
1271
1401
|
|
|
1272
1402
|
// src/core/native/filePicker.ts
|
|
1273
|
-
var
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
var DEFAULT_IMAGE_MIME_TYPE = "image/jpeg";
|
|
1277
|
-
var DEFAULT_DOCUMENT_MIME_TYPE = "application/octet-stream";
|
|
1278
|
-
var DEFAULT_IMAGE_SELECTION_LIMIT = 5;
|
|
1279
|
-
var DEFAULT_DOCUMENT_MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
|
|
1280
|
-
var DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024;
|
|
1403
|
+
var import_react_native2 = require("react-native");
|
|
1404
|
+
|
|
1405
|
+
// src/core/native/filePickerShared.ts
|
|
1281
1406
|
var SagepilotFilePickerError = class extends Error {
|
|
1407
|
+
/** Creates a typed picker error that can be surfaced over the widget bridge. */
|
|
1282
1408
|
constructor(code, message) {
|
|
1283
1409
|
super(message);
|
|
1284
1410
|
this.name = "SagepilotFilePickerError";
|
|
1285
1411
|
this.code = code;
|
|
1286
1412
|
}
|
|
1287
1413
|
};
|
|
1414
|
+
var SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION = 1920;
|
|
1415
|
+
var SAGEPILOT_DEFAULT_IMAGE_QUALITY = 0.8;
|
|
1416
|
+
var SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024;
|
|
1417
|
+
|
|
1418
|
+
// src/core/native/filePicker.ts
|
|
1419
|
+
var DEBUG_PREFIX = "[SagepilotSDK][FilePicker]";
|
|
1420
|
+
var DEFAULT_IMAGE_MAX_DIMENSION = SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION;
|
|
1421
|
+
var DEFAULT_IMAGE_QUALITY = SAGEPILOT_DEFAULT_IMAGE_QUALITY;
|
|
1422
|
+
var DEFAULT_IMAGE_MIME_TYPE = "image/jpeg";
|
|
1423
|
+
var DEFAULT_DOCUMENT_MIME_TYPE = "application/octet-stream";
|
|
1424
|
+
var DEFAULT_IMAGE_SELECTION_LIMIT = 5;
|
|
1425
|
+
var DEFAULT_DOCUMENT_MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
|
|
1426
|
+
var DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES = SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES;
|
|
1288
1427
|
function bytesToMb(bytes) {
|
|
1289
1428
|
return Math.round(bytes / (1024 * 1024) * 10) / 10;
|
|
1290
1429
|
}
|
|
@@ -1294,27 +1433,59 @@ function estimateBase64ByteSize(dataBase64) {
|
|
|
1294
1433
|
function stripFileScheme(uri) {
|
|
1295
1434
|
return uri.startsWith("file://") ? uri.replace("file://", "") : uri;
|
|
1296
1435
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1436
|
+
function debugFilePicker(message, details) {
|
|
1437
|
+
console.log(`${DEBUG_PREFIX} ${message}`, details ?? "");
|
|
1438
|
+
}
|
|
1439
|
+
async function promptOpenSagepilotCameraSettings() {
|
|
1440
|
+
if (import_react_native2.Platform.OS !== "android") return;
|
|
1441
|
+
await new Promise((resolve) => {
|
|
1442
|
+
import_react_native2.Alert.alert(
|
|
1443
|
+
"Camera access is off",
|
|
1444
|
+
"Enable camera access for this app in Settings to take a photo.",
|
|
1445
|
+
[
|
|
1446
|
+
{
|
|
1447
|
+
text: "Not now",
|
|
1448
|
+
style: "cancel",
|
|
1449
|
+
onPress: resolve
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
text: "Open Settings",
|
|
1453
|
+
onPress: () => {
|
|
1454
|
+
import_react_native2.Linking.openSettings().finally(resolve);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
]
|
|
1458
|
+
);
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
async function ensureSagepilotAndroidCameraPermission() {
|
|
1462
|
+
if (import_react_native2.Platform.OS !== "android") return;
|
|
1299
1463
|
let result;
|
|
1300
1464
|
try {
|
|
1301
|
-
|
|
1465
|
+
debugFilePicker("requesting Android camera permission");
|
|
1466
|
+
result = await import_react_native2.PermissionsAndroid.request(import_react_native2.PermissionsAndroid.PERMISSIONS.CAMERA);
|
|
1302
1467
|
} catch {
|
|
1468
|
+
debugFilePicker("camera permission request threw; continuing to native launch");
|
|
1303
1469
|
return;
|
|
1304
1470
|
}
|
|
1305
|
-
|
|
1471
|
+
debugFilePicker("Android camera permission result", { result });
|
|
1472
|
+
if (result === import_react_native2.PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
|
|
1473
|
+
await promptOpenSagepilotCameraSettings();
|
|
1306
1474
|
throw new SagepilotFilePickerError(
|
|
1307
1475
|
"permission_denied",
|
|
1308
1476
|
"Camera access is turned off. Enable it for this app in Settings to take a photo."
|
|
1309
1477
|
);
|
|
1310
1478
|
}
|
|
1311
|
-
if (result ===
|
|
1479
|
+
if (result === import_react_native2.PermissionsAndroid.RESULTS.DENIED) {
|
|
1312
1480
|
throw new SagepilotFilePickerError(
|
|
1313
1481
|
"permission_denied",
|
|
1314
1482
|
"Camera permission is required to take a photo."
|
|
1315
1483
|
);
|
|
1316
1484
|
}
|
|
1317
1485
|
}
|
|
1486
|
+
function canUseCameraSource(imagePicker) {
|
|
1487
|
+
return Boolean(imagePicker);
|
|
1488
|
+
}
|
|
1318
1489
|
function mapImagePickerErrorCode(errorCode) {
|
|
1319
1490
|
switch (errorCode) {
|
|
1320
1491
|
case "permission":
|
|
@@ -1391,8 +1562,16 @@ function createSagepilotFilePicker(options) {
|
|
|
1391
1562
|
const documentMaxFileSizeBytes = options.documentMaxFileSizeBytes ?? DEFAULT_DOCUMENT_MAX_FILE_SIZE_BYTES;
|
|
1392
1563
|
const imageMaxFileSizeBytes = options.imageMaxFileSizeBytes ?? DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES;
|
|
1393
1564
|
const sources = [];
|
|
1394
|
-
if (imagePicker) sources.push("camera"
|
|
1565
|
+
if (canUseCameraSource(imagePicker)) sources.push("camera");
|
|
1566
|
+
if (imagePicker) sources.push("library");
|
|
1395
1567
|
if (documentsPicker) sources.push("documents");
|
|
1568
|
+
debugFilePicker("adapter created", {
|
|
1569
|
+
platform: import_react_native2.Platform.OS,
|
|
1570
|
+
hasImagePicker: Boolean(imagePicker),
|
|
1571
|
+
hasDocumentsPicker: Boolean(documentsPicker),
|
|
1572
|
+
hasFileReader: Boolean(fileReader),
|
|
1573
|
+
sources
|
|
1574
|
+
});
|
|
1396
1575
|
if (sources.length === 0) return void 0;
|
|
1397
1576
|
const imageOptions = {
|
|
1398
1577
|
mediaType: "photo",
|
|
@@ -1403,12 +1582,22 @@ function createSagepilotFilePicker(options) {
|
|
|
1403
1582
|
saveToPhotos: false
|
|
1404
1583
|
};
|
|
1405
1584
|
async function pickFromCamera() {
|
|
1406
|
-
|
|
1407
|
-
|
|
1585
|
+
debugFilePicker("camera pick requested", {
|
|
1586
|
+
platform: import_react_native2.Platform.OS,
|
|
1587
|
+
imageMaxDimension,
|
|
1588
|
+
imageQuality,
|
|
1589
|
+
imageMaxFileSizeBytes
|
|
1590
|
+
});
|
|
1591
|
+
await ensureSagepilotAndroidCameraPermission();
|
|
1592
|
+
if (!imagePicker) {
|
|
1593
|
+
throw new SagepilotFilePickerError("camera_unavailable", "The camera picker is not available in this build.");
|
|
1594
|
+
}
|
|
1595
|
+
debugFilePicker("launching image-picker camera", { platform: import_react_native2.Platform.OS });
|
|
1408
1596
|
return imageAssetsToPickedFiles(await imagePicker.launchCamera(imageOptions), imageMaxFileSizeBytes);
|
|
1409
1597
|
}
|
|
1410
1598
|
async function pickFromLibrary(multiple) {
|
|
1411
1599
|
if (!imagePicker) return [];
|
|
1600
|
+
debugFilePicker("library pick requested", { multiple, imageSelectionLimit });
|
|
1412
1601
|
return imageAssetsToPickedFiles(await imagePicker.launchImageLibrary({
|
|
1413
1602
|
...imageOptions,
|
|
1414
1603
|
// Bounded multi-select: 0 (unlimited) lets a huge batch OOM the bridge.
|
|
@@ -1417,6 +1606,7 @@ function createSagepilotFilePicker(options) {
|
|
|
1417
1606
|
}
|
|
1418
1607
|
async function pickDocuments(multiple) {
|
|
1419
1608
|
if (!documentsPicker) return [];
|
|
1609
|
+
debugFilePicker("document pick requested", { multiple, documentMaxFileSizeBytes });
|
|
1420
1610
|
let picked;
|
|
1421
1611
|
try {
|
|
1422
1612
|
picked = await documentsPicker.pick({ allowMultiSelection: multiple });
|
|
@@ -1466,6 +1656,7 @@ function createSagepilotFilePicker(options) {
|
|
|
1466
1656
|
return {
|
|
1467
1657
|
sources,
|
|
1468
1658
|
async pickFiles(request) {
|
|
1659
|
+
debugFilePicker("pickFiles request", request);
|
|
1469
1660
|
if (request.source === "camera") return pickFromCamera();
|
|
1470
1661
|
if (request.source === "library") return pickFromLibrary(request.multiple);
|
|
1471
1662
|
return pickDocuments(request.multiple);
|
|
@@ -1475,13 +1666,17 @@ function createSagepilotFilePicker(options) {
|
|
|
1475
1666
|
|
|
1476
1667
|
// src/ui/SagepilotChatProvider.ts
|
|
1477
1668
|
var import_react = require("react");
|
|
1478
|
-
var
|
|
1669
|
+
var import_lucide_react_native = require("lucide-react-native");
|
|
1670
|
+
var import_react_native3 = require("react-native");
|
|
1479
1671
|
var import_react_native_webview = require("react-native-webview");
|
|
1480
1672
|
|
|
1481
1673
|
// src/core/webview/mobileBridge.ts
|
|
1482
1674
|
var FILE_PICKER_PROTOCOL_VERSION = 2;
|
|
1675
|
+
function buildBridgeCapabilitiesPayload(capabilities) {
|
|
1676
|
+
return { ...capabilities, filePickerProtocol: FILE_PICKER_PROTOCOL_VERSION };
|
|
1677
|
+
}
|
|
1483
1678
|
function buildBridgeCapabilitiesScript(capabilities) {
|
|
1484
|
-
const payload =
|
|
1679
|
+
const payload = buildBridgeCapabilitiesPayload(capabilities);
|
|
1485
1680
|
return [
|
|
1486
1681
|
"(function(){",
|
|
1487
1682
|
"try {",
|
|
@@ -1530,9 +1725,23 @@ function parseHostedBridgeMessage(rawData) {
|
|
|
1530
1725
|
return null;
|
|
1531
1726
|
}
|
|
1532
1727
|
}
|
|
1533
|
-
|
|
1728
|
+
function buildMobileWebViewBridgeScript(capabilities) {
|
|
1729
|
+
const payload = buildBridgeCapabilitiesPayload(capabilities);
|
|
1730
|
+
return `
|
|
1534
1731
|
(function () {
|
|
1535
|
-
|
|
1732
|
+
var bridgeCapabilities = ${JSON.stringify(payload)};
|
|
1733
|
+
var applyBridgeCapabilities = function () {
|
|
1734
|
+
try {
|
|
1735
|
+
if (window.SagepilotMobileBridge) {
|
|
1736
|
+
window.SagepilotMobileBridge.capabilities = Object.assign({}, window.SagepilotMobileBridge.capabilities, bridgeCapabilities);
|
|
1737
|
+
}
|
|
1738
|
+
} catch (error) {}
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
if (window.__sagepilotRnBridgeInstalled) {
|
|
1742
|
+
applyBridgeCapabilities();
|
|
1743
|
+
return true;
|
|
1744
|
+
}
|
|
1536
1745
|
window.__sagepilotRnBridgeInstalled = true;
|
|
1537
1746
|
|
|
1538
1747
|
var ensureViewport = function () {
|
|
@@ -1573,9 +1782,11 @@ var mobileWebViewBridgeScript = `
|
|
|
1573
1782
|
};
|
|
1574
1783
|
|
|
1575
1784
|
window.SagepilotMobileBridge = {
|
|
1785
|
+
capabilities: bridgeCapabilities,
|
|
1576
1786
|
postMessage: sendToReactNative,
|
|
1577
1787
|
ready: function () { sendToReactNative({ type: "sagepilot:ready" }); }
|
|
1578
1788
|
};
|
|
1789
|
+
applyBridgeCapabilities();
|
|
1579
1790
|
|
|
1580
1791
|
document.addEventListener("click", function (event) {
|
|
1581
1792
|
try {
|
|
@@ -1616,6 +1827,7 @@ var mobileWebViewBridgeScript = `
|
|
|
1616
1827
|
return true;
|
|
1617
1828
|
})();
|
|
1618
1829
|
`;
|
|
1830
|
+
}
|
|
1619
1831
|
|
|
1620
1832
|
// src/specs/SagepilotInsetsViewNativeComponent.ts
|
|
1621
1833
|
var import_codegenNativeComponent = __toESM(require("react-native/Libraries/Utilities/codegenNativeComponent"));
|
|
@@ -1632,13 +1844,17 @@ function readPresentationState() {
|
|
|
1632
1844
|
presentation: internalSagepilotChat.getConfig()?.presentation
|
|
1633
1845
|
};
|
|
1634
1846
|
}
|
|
1847
|
+
function getBridgeCapabilities() {
|
|
1848
|
+
return {
|
|
1849
|
+
nativeFilePicker: Boolean(internalSagepilotChat.getConfig()?.filePicker)
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1635
1852
|
function getInjectedWebViewScript() {
|
|
1853
|
+
const capabilities = getBridgeCapabilities();
|
|
1636
1854
|
return [
|
|
1637
1855
|
internalSagepilotChat.getHostedAuthScript(),
|
|
1638
|
-
|
|
1639
|
-
buildBridgeCapabilitiesScript(
|
|
1640
|
-
nativeFilePicker: Boolean(internalSagepilotChat.getConfig()?.filePicker)
|
|
1641
|
-
})
|
|
1856
|
+
buildMobileWebViewBridgeScript(capabilities),
|
|
1857
|
+
buildBridgeCapabilitiesScript(capabilities)
|
|
1642
1858
|
].filter(Boolean).join("\n");
|
|
1643
1859
|
}
|
|
1644
1860
|
var FILE_PICKER_SOURCE_LABELS = {
|
|
@@ -1646,6 +1862,22 @@ var FILE_PICKER_SOURCE_LABELS = {
|
|
|
1646
1862
|
library: "Choose from gallery",
|
|
1647
1863
|
documents: "Browse files"
|
|
1648
1864
|
};
|
|
1865
|
+
var FILE_PICKER_SOURCE_DESCRIPTIONS = {
|
|
1866
|
+
camera: "Use the in-app camera",
|
|
1867
|
+
library: "Photos and videos on this device",
|
|
1868
|
+
documents: "PDFs, documents, and Drive files"
|
|
1869
|
+
};
|
|
1870
|
+
var FILE_PICKER_SOURCE_ICONS = {
|
|
1871
|
+
camera: import_lucide_react_native.Camera,
|
|
1872
|
+
library: import_lucide_react_native.Images,
|
|
1873
|
+
documents: import_lucide_react_native.FileText
|
|
1874
|
+
};
|
|
1875
|
+
var FILE_PICKER_SOURCE_ICON_COLORS = {
|
|
1876
|
+
camera: "#0284c7",
|
|
1877
|
+
library: "#16a34a",
|
|
1878
|
+
documents: "#7c3aed"
|
|
1879
|
+
};
|
|
1880
|
+
var DEBUG_PREFIX2 = "[SagepilotSDK][Provider]";
|
|
1649
1881
|
var DELIVERY_RETRY_MS = 1500;
|
|
1650
1882
|
var DELIVERY_LEGACY_FALLBACK_ATTEMPTS = 2;
|
|
1651
1883
|
var DELIVERY_MAX_ATTEMPTS = 8;
|
|
@@ -1655,6 +1887,7 @@ var PENDING_FILES_CACHE_KEY = "pending_batch";
|
|
|
1655
1887
|
var LIVENESS_HEARTBEAT_STALE_MS = 4e3;
|
|
1656
1888
|
var LIVENESS_PONG_TIMEOUT_MS = 1500;
|
|
1657
1889
|
var LIVENESS_MAX_PING_ATTEMPTS = 2;
|
|
1890
|
+
var LIVENESS_PICKER_GRACE_MS = 8e3;
|
|
1658
1891
|
var LIVENESS_MAX_REMOUNTS = 2;
|
|
1659
1892
|
var batchSequence = 0;
|
|
1660
1893
|
function nextBatchId() {
|
|
@@ -1668,6 +1901,29 @@ function totalBase64Bytes(files) {
|
|
|
1668
1901
|
function storageKeyFor(batchId, index) {
|
|
1669
1902
|
return `${batchId}_${index}.bin`;
|
|
1670
1903
|
}
|
|
1904
|
+
function renderFilePickerSourceIcon(source) {
|
|
1905
|
+
const Icon = FILE_PICKER_SOURCE_ICONS[source];
|
|
1906
|
+
return (0, import_react.createElement)(
|
|
1907
|
+
import_react_native3.View,
|
|
1908
|
+
{ style: getFilePickerSourceIconStyle(source) },
|
|
1909
|
+
(0, import_react.createElement)(Icon, {
|
|
1910
|
+
color: FILE_PICKER_SOURCE_ICON_COLORS[source],
|
|
1911
|
+
size: 23,
|
|
1912
|
+
strokeWidth: 2.35
|
|
1913
|
+
})
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
function getFilePickerSourceIconStyle(source) {
|
|
1917
|
+
if (source === "camera") return [styles.sheetOptionIcon, styles.cameraOptionIcon];
|
|
1918
|
+
if (source === "library") return [styles.sheetOptionIcon, styles.libraryOptionIcon];
|
|
1919
|
+
return [styles.sheetOptionIcon, styles.documentsOptionIcon];
|
|
1920
|
+
}
|
|
1921
|
+
function getSheetOptionStyle(pressed) {
|
|
1922
|
+
return [styles.sheetOption, pressed && styles.sheetOptionPressed];
|
|
1923
|
+
}
|
|
1924
|
+
function getSheetCancelStyle(pressed) {
|
|
1925
|
+
return [styles.sheetCancelButton, pressed && styles.sheetCancelButtonPressed];
|
|
1926
|
+
}
|
|
1671
1927
|
function buildDispatchScript(messageLiteral) {
|
|
1672
1928
|
return [
|
|
1673
1929
|
"(function(){",
|
|
@@ -1679,8 +1935,14 @@ function buildDispatchScript(messageLiteral) {
|
|
|
1679
1935
|
"})();"
|
|
1680
1936
|
].join("\n");
|
|
1681
1937
|
}
|
|
1682
|
-
function buildFilesPickedScript(
|
|
1683
|
-
return buildDispatchScript(
|
|
1938
|
+
function buildFilesPickedScript(batch) {
|
|
1939
|
+
return buildDispatchScript(JSON.stringify({
|
|
1940
|
+
type: "sagepilot:files_picked",
|
|
1941
|
+
batch_id: batch.batchId,
|
|
1942
|
+
chat_id: batch.target?.chatId,
|
|
1943
|
+
conversation_id: batch.target?.chatId,
|
|
1944
|
+
files: batch.files
|
|
1945
|
+
}));
|
|
1684
1946
|
}
|
|
1685
1947
|
function buildFilePickerErrorScript(message, code) {
|
|
1686
1948
|
return buildDispatchScript(
|
|
@@ -1699,6 +1961,16 @@ function readFilePickerError(error) {
|
|
|
1699
1961
|
}
|
|
1700
1962
|
return { message: "Could not attach the selected file." };
|
|
1701
1963
|
}
|
|
1964
|
+
function debugProvider(message, details) {
|
|
1965
|
+
console.log(`${DEBUG_PREFIX2} ${message}`, details ?? "");
|
|
1966
|
+
}
|
|
1967
|
+
function describeAttachmentTarget(target) {
|
|
1968
|
+
return {
|
|
1969
|
+
targetChatId: target?.chatId,
|
|
1970
|
+
targetRouteChatId: target?.routeChatId,
|
|
1971
|
+
targetScreen: target?.hostedChatView.screen
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1702
1974
|
function getHostedIdentityDispatchScript() {
|
|
1703
1975
|
const message = internalSagepilotChat.getHostedIdentityMessage();
|
|
1704
1976
|
if (!message) return "";
|
|
@@ -1727,7 +1999,7 @@ function isInternalWebViewScheme(url) {
|
|
|
1727
1999
|
var hostedChatWebViewProps = {
|
|
1728
2000
|
allowFileAccess: true,
|
|
1729
2001
|
allowFileAccessFromFileURLs: true,
|
|
1730
|
-
...
|
|
2002
|
+
...import_react_native3.Platform.OS === "ios" ? {
|
|
1731
2003
|
automaticallyAdjustContentInsets: false,
|
|
1732
2004
|
contentInsetAdjustmentBehavior: "never",
|
|
1733
2005
|
hideKeyboardAccessoryView: true
|
|
@@ -1742,7 +2014,7 @@ var hostedChatWebViewProps = {
|
|
|
1742
2014
|
sharedCookiesEnabled: true,
|
|
1743
2015
|
thirdPartyCookiesEnabled: true
|
|
1744
2016
|
};
|
|
1745
|
-
var AndroidInsetsView =
|
|
2017
|
+
var AndroidInsetsView = import_react_native3.Platform.OS === "android" ? SagepilotInsetsViewNativeComponent_default : import_react_native3.View;
|
|
1746
2018
|
var emptyAndroidInsets = { top: 0, bottom: 0 };
|
|
1747
2019
|
function SagepilotChatProvider({
|
|
1748
2020
|
children,
|
|
@@ -1761,8 +2033,11 @@ function SagepilotChatProvider({
|
|
|
1761
2033
|
const deliveryAttemptsRef = (0, import_react.useRef)(0);
|
|
1762
2034
|
const pendingPingRef = (0, import_react.useRef)(null);
|
|
1763
2035
|
const pingTimeoutRef = (0, import_react.useRef)(null);
|
|
2036
|
+
const livenessResumeTimerRef = (0, import_react.useRef)(null);
|
|
2037
|
+
const livenessPausedUntilRef = (0, import_react.useRef)(0);
|
|
2038
|
+
const pickerInFlightRef = (0, import_react.useRef)(false);
|
|
1764
2039
|
const livenessRemountCountRef = (0, import_react.useRef)(0);
|
|
1765
|
-
const appStateRef = (0, import_react.useRef)(
|
|
2040
|
+
const appStateRef = (0, import_react.useRef)(import_react_native3.AppState.currentState);
|
|
1766
2041
|
const didReconcileRef = (0, import_react.useRef)(false);
|
|
1767
2042
|
const [androidRepaintTick, setAndroidRepaintTick] = (0, import_react.useState)(0);
|
|
1768
2043
|
const [sourceChooser, setSourceChooser] = (0, import_react.useState)(null);
|
|
@@ -1783,6 +2058,7 @@ function SagepilotChatProvider({
|
|
|
1783
2058
|
if (hasFileStore) {
|
|
1784
2059
|
const manifest = queue.filter((batch) => batch.storageKeys && batch.storageKeys.length === batch.files.length).map((batch) => ({
|
|
1785
2060
|
batchId: batch.batchId,
|
|
2061
|
+
target: batch.target,
|
|
1786
2062
|
files: batch.files.map((file, index) => ({
|
|
1787
2063
|
file_name: file.file_name,
|
|
1788
2064
|
mime_type: file.mime_type,
|
|
@@ -1801,6 +2077,7 @@ function SagepilotChatProvider({
|
|
|
1801
2077
|
if (totalBytes <= PERSIST_MAX_TOTAL_BYTES) {
|
|
1802
2078
|
const manifest = queue.map((batch) => ({
|
|
1803
2079
|
batchId: batch.batchId,
|
|
2080
|
+
target: batch.target,
|
|
1804
2081
|
files: batch.files.map((file) => ({
|
|
1805
2082
|
file_name: file.file_name,
|
|
1806
2083
|
mime_type: file.mime_type,
|
|
@@ -1842,12 +2119,28 @@ function SagepilotChatProvider({
|
|
|
1842
2119
|
}
|
|
1843
2120
|
const batch = pendingBatchesRef.current[0];
|
|
1844
2121
|
if (!batch) return;
|
|
2122
|
+
if (internalSagepilotChat.restoreHostedAttachmentTarget(batch.target)) {
|
|
2123
|
+
debugProvider("restored hosted attachment target", {
|
|
2124
|
+
batchId: batch.batchId,
|
|
2125
|
+
...describeAttachmentTarget(batch.target)
|
|
2126
|
+
});
|
|
2127
|
+
widgetReadyRef.current = false;
|
|
2128
|
+
deliveryTimerRef.current = setTimeout(() => pumpDelivery(), DELIVERY_RETRY_MS);
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
1845
2131
|
deliveryAttemptsRef.current += 1;
|
|
1846
2132
|
const attempts = deliveryAttemptsRef.current;
|
|
1847
2133
|
const ref = nativeWebViewRef.current;
|
|
1848
2134
|
const shouldDeliver = ref && (widgetReadyRef.current || attempts >= DELIVERY_LEGACY_FALLBACK_ATTEMPTS);
|
|
1849
2135
|
if (shouldDeliver && ref) {
|
|
1850
|
-
|
|
2136
|
+
debugProvider("delivering native picked files", {
|
|
2137
|
+
batchId: batch.batchId,
|
|
2138
|
+
attempt: attempts,
|
|
2139
|
+
widgetReady: widgetReadyRef.current,
|
|
2140
|
+
count: batch.files.length,
|
|
2141
|
+
...describeAttachmentTarget(batch.target)
|
|
2142
|
+
});
|
|
2143
|
+
ref.injectJavaScript(buildFilesPickedScript(batch));
|
|
1851
2144
|
}
|
|
1852
2145
|
if (attempts < DELIVERY_MAX_ATTEMPTS) {
|
|
1853
2146
|
deliveryTimerRef.current = setTimeout(() => pumpDelivery(), DELIVERY_RETRY_MS);
|
|
@@ -1878,23 +2171,65 @@ function SagepilotChatProvider({
|
|
|
1878
2171
|
const batchId = nextBatchId();
|
|
1879
2172
|
const hasFileStore = Boolean(internalSagepilotChat.getConfig()?.fileStore);
|
|
1880
2173
|
const storageKeys = hasFileStore ? files.map((_, index) => storageKeyFor(batchId, index)) : void 0;
|
|
1881
|
-
const batch = {
|
|
2174
|
+
const batch = {
|
|
2175
|
+
batchId,
|
|
2176
|
+
files,
|
|
2177
|
+
storageKeys,
|
|
2178
|
+
target: internalSagepilotChat.getHostedAttachmentTarget()
|
|
2179
|
+
};
|
|
2180
|
+
debugProvider("queued native picked files", {
|
|
2181
|
+
batchId,
|
|
2182
|
+
count: files.length,
|
|
2183
|
+
totalBase64Bytes: totalBase64Bytes(files),
|
|
2184
|
+
persistedToFileStore: hasFileStore,
|
|
2185
|
+
...describeAttachmentTarget(batch.target)
|
|
2186
|
+
});
|
|
1882
2187
|
pendingBatchesRef.current = [...pendingBatchesRef.current, batch];
|
|
1883
2188
|
ensureDelivery();
|
|
1884
2189
|
writeManifest();
|
|
1885
2190
|
void writeBatchBytes(batch);
|
|
1886
2191
|
}, [ensureDelivery, writeManifest, writeBatchBytes]);
|
|
1887
|
-
const recoverNativeWebView = (0, import_react.useCallback)(() => {
|
|
2192
|
+
const recoverNativeWebView = (0, import_react.useCallback)((reasonOrEvent) => {
|
|
2193
|
+
const reason = typeof reasonOrEvent === "string" ? reasonOrEvent : "webview_render_process";
|
|
2194
|
+
debugProvider("recovering native WebView", { reason });
|
|
1888
2195
|
widgetReadyRef.current = false;
|
|
1889
2196
|
setNativeWebViewKey((key) => key + 1);
|
|
1890
2197
|
}, []);
|
|
1891
2198
|
const recoverPreloadWebView = (0, import_react.useCallback)(() => {
|
|
1892
2199
|
setPreloadWebViewKey((key) => key + 1);
|
|
1893
2200
|
}, []);
|
|
2201
|
+
const pauseLivenessAfterPicker = (0, import_react.useCallback)((durationMs) => {
|
|
2202
|
+
if (import_react_native3.Platform.OS !== "android") return;
|
|
2203
|
+
livenessPausedUntilRef.current = Math.max(livenessPausedUntilRef.current, Date.now() + durationMs);
|
|
2204
|
+
}, []);
|
|
1894
2205
|
const runNativeFilePicker = (0, import_react.useCallback)((source, multiple) => {
|
|
1895
2206
|
const filePicker = internalSagepilotChat.getConfig()?.filePicker;
|
|
1896
|
-
if (!filePicker)
|
|
2207
|
+
if (!filePicker) {
|
|
2208
|
+
debugProvider("native file picker requested but no adapter is configured", { source, multiple });
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
if (pickerInFlightRef.current) {
|
|
2212
|
+
debugProvider("native file picker ignored because another picker is in flight", { source, multiple });
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
debugProvider("native file picker starting", {
|
|
2216
|
+
source,
|
|
2217
|
+
multiple,
|
|
2218
|
+
configuredSources: filePicker.sources
|
|
2219
|
+
});
|
|
2220
|
+
pauseLivenessAfterPicker(6e4);
|
|
2221
|
+
pickerInFlightRef.current = true;
|
|
1897
2222
|
filePicker.pickFiles({ source, multiple }).then((files) => {
|
|
2223
|
+
debugProvider("native file picker resolved", {
|
|
2224
|
+
source,
|
|
2225
|
+
count: files.length,
|
|
2226
|
+
files: files.map((file) => ({
|
|
2227
|
+
fileName: file.file_name,
|
|
2228
|
+
mimeType: file.mime_type,
|
|
2229
|
+
size: file.size,
|
|
2230
|
+
base64Length: file.data_base64.length
|
|
2231
|
+
}))
|
|
2232
|
+
});
|
|
1898
2233
|
if (files.length === 0) {
|
|
1899
2234
|
nativeWebViewRef.current?.injectJavaScript(buildFilePickerCancelledScript());
|
|
1900
2235
|
return;
|
|
@@ -1902,14 +2237,26 @@ function SagepilotChatProvider({
|
|
|
1902
2237
|
queuePickedFiles(files);
|
|
1903
2238
|
}).catch((error) => {
|
|
1904
2239
|
const { message, code } = readFilePickerError(error);
|
|
2240
|
+
debugProvider("native file picker failed", { source, code, message });
|
|
1905
2241
|
nativeWebViewRef.current?.injectJavaScript(buildFilePickerErrorScript(message, code));
|
|
2242
|
+
}).finally(() => {
|
|
2243
|
+
pickerInFlightRef.current = false;
|
|
2244
|
+
pauseLivenessAfterPicker(LIVENESS_PICKER_GRACE_MS);
|
|
1906
2245
|
});
|
|
1907
|
-
}, [queuePickedFiles]);
|
|
2246
|
+
}, [pauseLivenessAfterPicker, queuePickedFiles]);
|
|
1908
2247
|
const openNativeFilePicker = (0, import_react.useCallback)((multiple) => {
|
|
1909
2248
|
const filePicker = internalSagepilotChat.getConfig()?.filePicker;
|
|
1910
|
-
if (!filePicker || filePicker.sources.length === 0)
|
|
2249
|
+
if (!filePicker || filePicker.sources.length === 0) {
|
|
2250
|
+
debugProvider("open native file picker ignored", {
|
|
2251
|
+
multiple,
|
|
2252
|
+
hasAdapter: Boolean(filePicker),
|
|
2253
|
+
sources: filePicker?.sources ?? []
|
|
2254
|
+
});
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
debugProvider("open native file picker", { multiple, sources: filePicker.sources });
|
|
1911
2258
|
const [onlySource] = filePicker.sources;
|
|
1912
|
-
if (filePicker.sources.length === 1 && onlySource) {
|
|
2259
|
+
if (filePicker.sources.length === 1 && onlySource && onlySource !== "camera") {
|
|
1913
2260
|
runNativeFilePicker(onlySource, multiple);
|
|
1914
2261
|
return;
|
|
1915
2262
|
}
|
|
@@ -1962,7 +2309,8 @@ function SagepilotChatProvider({
|
|
|
1962
2309
|
restored.push({
|
|
1963
2310
|
batchId: batch.batchId,
|
|
1964
2311
|
files,
|
|
1965
|
-
storageKeys: storageKeys.length === files.length ? storageKeys : void 0
|
|
2312
|
+
storageKeys: storageKeys.length === files.length ? storageKeys : void 0,
|
|
2313
|
+
target: batch.target
|
|
1966
2314
|
});
|
|
1967
2315
|
}
|
|
1968
2316
|
}
|
|
@@ -1983,6 +2331,19 @@ function SagepilotChatProvider({
|
|
|
1983
2331
|
cancelled = true;
|
|
1984
2332
|
};
|
|
1985
2333
|
}, [getPendingCache, startDelivery, writeManifest]);
|
|
2334
|
+
const clearPing = (0, import_react.useCallback)(() => {
|
|
2335
|
+
pendingPingRef.current = null;
|
|
2336
|
+
if (pingTimeoutRef.current) {
|
|
2337
|
+
clearTimeout(pingTimeoutRef.current);
|
|
2338
|
+
pingTimeoutRef.current = null;
|
|
2339
|
+
}
|
|
2340
|
+
}, []);
|
|
2341
|
+
const clearDeferredLivenessProbe = (0, import_react.useCallback)(() => {
|
|
2342
|
+
if (livenessResumeTimerRef.current) {
|
|
2343
|
+
clearTimeout(livenessResumeTimerRef.current);
|
|
2344
|
+
livenessResumeTimerRef.current = null;
|
|
2345
|
+
}
|
|
2346
|
+
}, []);
|
|
1986
2347
|
(0, import_react.useEffect)(() => {
|
|
1987
2348
|
return () => {
|
|
1988
2349
|
if (deliveryTimerRef.current) {
|
|
@@ -1993,19 +2354,27 @@ function SagepilotChatProvider({
|
|
|
1993
2354
|
clearTimeout(pingTimeoutRef.current);
|
|
1994
2355
|
pingTimeoutRef.current = null;
|
|
1995
2356
|
}
|
|
2357
|
+
clearDeferredLivenessProbe();
|
|
1996
2358
|
};
|
|
1997
|
-
}, []);
|
|
1998
|
-
const clearPing = (0, import_react.useCallback)(() => {
|
|
1999
|
-
pendingPingRef.current = null;
|
|
2000
|
-
if (pingTimeoutRef.current) {
|
|
2001
|
-
clearTimeout(pingTimeoutRef.current);
|
|
2002
|
-
pingTimeoutRef.current = null;
|
|
2003
|
-
}
|
|
2004
|
-
}, []);
|
|
2359
|
+
}, [clearDeferredLivenessProbe]);
|
|
2005
2360
|
const runLivenessProbe = (0, import_react.useCallback)(() => {
|
|
2006
|
-
if (
|
|
2361
|
+
if (import_react_native3.Platform.OS !== "android") return;
|
|
2007
2362
|
const ref = nativeWebViewRef.current;
|
|
2008
2363
|
if (!ref || !internalSagepilotChat.isPresented()) return;
|
|
2364
|
+
const pauseRemainingMs = livenessPausedUntilRef.current - Date.now();
|
|
2365
|
+
if (pickerInFlightRef.current || pauseRemainingMs > 0) {
|
|
2366
|
+
const retryDelayMs = pickerInFlightRef.current ? 500 : Math.max(0, pauseRemainingMs);
|
|
2367
|
+
debugProvider("liveness probe deferred after picker", {
|
|
2368
|
+
pickerInFlight: pickerInFlightRef.current,
|
|
2369
|
+
retryDelayMs
|
|
2370
|
+
});
|
|
2371
|
+
clearDeferredLivenessProbe();
|
|
2372
|
+
livenessResumeTimerRef.current = setTimeout(() => {
|
|
2373
|
+
livenessResumeTimerRef.current = null;
|
|
2374
|
+
runLivenessProbe();
|
|
2375
|
+
}, retryDelayMs);
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2009
2378
|
setAndroidRepaintTick((tick) => tick + 1);
|
|
2010
2379
|
const attempts = (pendingPingRef.current?.attempts ?? 0) + 1;
|
|
2011
2380
|
const nonce = `${Date.now().toString(36)}_${attempts}`;
|
|
@@ -2015,14 +2384,14 @@ function SagepilotChatProvider({
|
|
|
2015
2384
|
pingTimeoutRef.current = setTimeout(() => {
|
|
2016
2385
|
if (attempts >= LIVENESS_MAX_PING_ATTEMPTS) {
|
|
2017
2386
|
clearPing();
|
|
2018
|
-
recoverNativeWebView();
|
|
2387
|
+
recoverNativeWebView("liveness_timeout");
|
|
2019
2388
|
return;
|
|
2020
2389
|
}
|
|
2021
2390
|
runLivenessProbe();
|
|
2022
2391
|
}, LIVENESS_PONG_TIMEOUT_MS);
|
|
2023
|
-
}, [clearPing, recoverNativeWebView]);
|
|
2392
|
+
}, [clearDeferredLivenessProbe, clearPing, recoverNativeWebView]);
|
|
2024
2393
|
(0, import_react.useEffect)(() => {
|
|
2025
|
-
const subscription =
|
|
2394
|
+
const subscription = import_react_native3.AppState.addEventListener("change", (nextState) => {
|
|
2026
2395
|
const prev = appStateRef.current;
|
|
2027
2396
|
appStateRef.current = nextState;
|
|
2028
2397
|
if (nextState === "active" && (prev === "background" || prev === "inactive")) {
|
|
@@ -2050,11 +2419,11 @@ function SagepilotChatProvider({
|
|
|
2050
2419
|
const presentationStyle = state.presentation?.style ?? "sheet";
|
|
2051
2420
|
const isFullScreenModal = presentationStyle === "fullScreen";
|
|
2052
2421
|
const animationType = presentationStyle === "fullScreen" || presentationStyle === "push" ? "slide" : "fade";
|
|
2053
|
-
const ModalContainer =
|
|
2054
|
-
const NativeModalContainer =
|
|
2055
|
-
const ChatContentContainer =
|
|
2056
|
-
const nativeModalContainerProps =
|
|
2057
|
-
const chatContentContainerProps =
|
|
2422
|
+
const ModalContainer = import_react_native3.Platform.OS === "ios" && isFullScreenModal ? import_react_native3.SafeAreaView : import_react_native3.View;
|
|
2423
|
+
const NativeModalContainer = import_react_native3.Platform.OS === "android" ? AndroidInsetsView : ModalContainer;
|
|
2424
|
+
const ChatContentContainer = import_react_native3.Platform.OS === "ios" ? import_react_native3.KeyboardAvoidingView : import_react_native3.View;
|
|
2425
|
+
const nativeModalContainerProps = import_react_native3.Platform.OS === "android" ? { style: styles.container, onInsetsChange: handleAndroidInsetsChange } : { style: styles.container };
|
|
2426
|
+
const chatContentContainerProps = import_react_native3.Platform.OS === "ios" ? {
|
|
2058
2427
|
behavior: "padding",
|
|
2059
2428
|
enabled: true,
|
|
2060
2429
|
keyboardVerticalOffset: 0,
|
|
@@ -2062,7 +2431,7 @@ function SagepilotChatProvider({
|
|
|
2062
2431
|
} : {
|
|
2063
2432
|
style: [
|
|
2064
2433
|
styles.modalContent,
|
|
2065
|
-
|
|
2434
|
+
import_react_native3.Platform.OS === "android" ? {
|
|
2066
2435
|
paddingTop: androidModalInsets.top,
|
|
2067
2436
|
paddingBottom: androidModalInsets.bottom
|
|
2068
2437
|
} : null
|
|
@@ -2083,6 +2452,11 @@ function SagepilotChatProvider({
|
|
|
2083
2452
|
const ackBatchId = message.batch_id;
|
|
2084
2453
|
const head = pendingBatchesRef.current[0];
|
|
2085
2454
|
if (head && (!ackBatchId || ackBatchId === head.batchId)) {
|
|
2455
|
+
debugProvider("native picked files acknowledged", {
|
|
2456
|
+
batchId: head.batchId,
|
|
2457
|
+
ackBatchId,
|
|
2458
|
+
count: message.count
|
|
2459
|
+
});
|
|
2086
2460
|
acknowledgeHeadBatch();
|
|
2087
2461
|
}
|
|
2088
2462
|
return;
|
|
@@ -2092,9 +2466,13 @@ function SagepilotChatProvider({
|
|
|
2092
2466
|
if (pending && (!message.nonce || message.nonce === pending.nonce)) {
|
|
2093
2467
|
if (message.alive === false) {
|
|
2094
2468
|
clearPing();
|
|
2469
|
+
debugProvider("liveness probe reported dead widget", {
|
|
2470
|
+
supportsHeartbeat: message.supportsHeartbeat,
|
|
2471
|
+
remountCount: livenessRemountCountRef.current
|
|
2472
|
+
});
|
|
2095
2473
|
if (livenessRemountCountRef.current < LIVENESS_MAX_REMOUNTS) {
|
|
2096
2474
|
livenessRemountCountRef.current += 1;
|
|
2097
|
-
recoverNativeWebView();
|
|
2475
|
+
recoverNativeWebView("liveness_dead_pong");
|
|
2098
2476
|
}
|
|
2099
2477
|
} else {
|
|
2100
2478
|
livenessRemountCountRef.current = 0;
|
|
@@ -2116,7 +2494,13 @@ function SagepilotChatProvider({
|
|
|
2116
2494
|
if (!script || !nativeWebViewRef.current) return;
|
|
2117
2495
|
nativeWebViewRef.current.injectJavaScript(script);
|
|
2118
2496
|
};
|
|
2497
|
+
const injectNativeBridgeToNativeWebView = () => {
|
|
2498
|
+
if (!nativeWebViewRef.current) return;
|
|
2499
|
+
nativeWebViewRef.current.injectJavaScript(getInjectedWebViewScript());
|
|
2500
|
+
};
|
|
2119
2501
|
const handleNativeWebViewLoadEnd = () => {
|
|
2502
|
+
debugProvider("native WebView load end");
|
|
2503
|
+
injectNativeBridgeToNativeWebView();
|
|
2120
2504
|
postIdentityToNativeWebView();
|
|
2121
2505
|
widgetReadyRef.current = false;
|
|
2122
2506
|
startDelivery();
|
|
@@ -2130,11 +2514,11 @@ function SagepilotChatProvider({
|
|
|
2130
2514
|
if (widgetOrigin && readUrlOrigin(url) === widgetOrigin) {
|
|
2131
2515
|
return true;
|
|
2132
2516
|
}
|
|
2133
|
-
|
|
2517
|
+
import_react_native3.Linking.openURL(url).catch(() => void 0);
|
|
2134
2518
|
return false;
|
|
2135
2519
|
}, [state.conversationUrl, state.preloadUrl]);
|
|
2136
2520
|
(0, import_react.useEffect)(() => {
|
|
2137
|
-
if (
|
|
2521
|
+
if (import_react_native3.Platform.OS !== "web" || typeof window === "undefined") return;
|
|
2138
2522
|
const trustedWidgetOrigin = readUrlOrigin(state.conversationUrl);
|
|
2139
2523
|
if (!trustedWidgetOrigin) return;
|
|
2140
2524
|
const handleWindowMessage = (event) => {
|
|
@@ -2145,16 +2529,16 @@ function SagepilotChatProvider({
|
|
|
2145
2529
|
return () => window.removeEventListener("message", handleWindowMessage);
|
|
2146
2530
|
}, [state.conversationUrl]);
|
|
2147
2531
|
(0, import_react.useEffect)(() => {
|
|
2148
|
-
if (
|
|
2532
|
+
if (import_react_native3.Platform.OS !== "web" || !state.isPresented) return;
|
|
2149
2533
|
postIdentityToWebFrame();
|
|
2150
2534
|
}, [state.isPresented, state.conversationUrl, state.configured]);
|
|
2151
2535
|
(0, import_react.useEffect)(() => {
|
|
2152
|
-
if (
|
|
2536
|
+
if (import_react_native3.Platform.OS === "web" || !state.isPresented) return;
|
|
2153
2537
|
postIdentityToNativeWebView();
|
|
2154
2538
|
}, [state.isPresented, state.conversationUrl, state.configured]);
|
|
2155
|
-
if (
|
|
2539
|
+
if (import_react_native3.Platform.OS === "web") {
|
|
2156
2540
|
return (0, import_react.createElement)(
|
|
2157
|
-
|
|
2541
|
+
import_react_native3.View,
|
|
2158
2542
|
{ style: styles.root },
|
|
2159
2543
|
children,
|
|
2160
2544
|
state.configured && !state.isPresented && state.shouldPreload && state.preloadUrl ? (0, import_react.createElement)("iframe", {
|
|
@@ -2163,20 +2547,20 @@ function SagepilotChatProvider({
|
|
|
2163
2547
|
title: "Sagepilot chat preload"
|
|
2164
2548
|
}) : null,
|
|
2165
2549
|
state.configured && state.isPresented && state.conversationUrl ? (0, import_react.createElement)(
|
|
2166
|
-
|
|
2550
|
+
import_react_native3.View,
|
|
2167
2551
|
{ style: styles.webOverlay },
|
|
2168
2552
|
(0, import_react.createElement)(
|
|
2169
|
-
|
|
2553
|
+
import_react_native3.View,
|
|
2170
2554
|
{ style: styles.webPanel },
|
|
2171
2555
|
showCloseButton ? (0, import_react.createElement)(
|
|
2172
|
-
|
|
2556
|
+
import_react_native3.Pressable,
|
|
2173
2557
|
{
|
|
2174
2558
|
accessibilityRole: "button",
|
|
2175
2559
|
accessibilityLabel: closeLabel,
|
|
2176
2560
|
onPress: () => internalSagepilotChat.dismiss(),
|
|
2177
2561
|
style: styles.webCloseButton
|
|
2178
2562
|
},
|
|
2179
|
-
(0, import_react.createElement)(
|
|
2563
|
+
(0, import_react.createElement)(import_react_native3.Text, { style: styles.closeText }, closeLabel)
|
|
2180
2564
|
) : null,
|
|
2181
2565
|
(0, import_react.createElement)("iframe", {
|
|
2182
2566
|
ref: webFrameRef,
|
|
@@ -2190,7 +2574,7 @@ function SagepilotChatProvider({
|
|
|
2190
2574
|
);
|
|
2191
2575
|
}
|
|
2192
2576
|
return (0, import_react.createElement)(
|
|
2193
|
-
|
|
2577
|
+
import_react_native3.View,
|
|
2194
2578
|
{ style: styles.root },
|
|
2195
2579
|
children,
|
|
2196
2580
|
state.configured && !state.isPresented && state.shouldPreload && state.preloadUrl ? (0, import_react.createElement)(import_react_native_webview.WebView, {
|
|
@@ -2209,13 +2593,13 @@ function SagepilotChatProvider({
|
|
|
2209
2593
|
onContentProcessDidTerminate: recoverPreloadWebView
|
|
2210
2594
|
}) : null,
|
|
2211
2595
|
(0, import_react.createElement)(
|
|
2212
|
-
|
|
2596
|
+
import_react_native3.Modal,
|
|
2213
2597
|
{
|
|
2214
2598
|
visible: state.configured && state.isPresented,
|
|
2215
2599
|
animationType,
|
|
2216
2600
|
presentationStyle: isFullScreenModal ? "fullScreen" : "pageSheet",
|
|
2217
|
-
statusBarTranslucent:
|
|
2218
|
-
navigationBarTranslucent:
|
|
2601
|
+
statusBarTranslucent: import_react_native3.Platform.OS === "android",
|
|
2602
|
+
navigationBarTranslucent: import_react_native3.Platform.OS === "android",
|
|
2219
2603
|
onRequestClose: () => internalSagepilotChat.dismiss()
|
|
2220
2604
|
},
|
|
2221
2605
|
(0, import_react.createElement)(
|
|
@@ -2225,17 +2609,17 @@ function SagepilotChatProvider({
|
|
|
2225
2609
|
ChatContentContainer,
|
|
2226
2610
|
chatContentContainerProps,
|
|
2227
2611
|
showCloseButton ? (0, import_react.createElement)(
|
|
2228
|
-
|
|
2612
|
+
import_react_native3.View,
|
|
2229
2613
|
{ style: styles.header },
|
|
2230
2614
|
(0, import_react.createElement)(
|
|
2231
|
-
|
|
2615
|
+
import_react_native3.Pressable,
|
|
2232
2616
|
{
|
|
2233
2617
|
accessibilityRole: "button",
|
|
2234
2618
|
accessibilityLabel: closeLabel,
|
|
2235
2619
|
onPress: () => internalSagepilotChat.dismiss(),
|
|
2236
2620
|
style: styles.closeButton
|
|
2237
2621
|
},
|
|
2238
|
-
(0, import_react.createElement)(
|
|
2622
|
+
(0, import_react.createElement)(import_react_native3.Text, { style: styles.closeText }, closeLabel)
|
|
2239
2623
|
)
|
|
2240
2624
|
) : null,
|
|
2241
2625
|
state.conversationUrl ? (0, import_react.createElement)(import_react_native_webview.WebView, {
|
|
@@ -2246,7 +2630,7 @@ function SagepilotChatProvider({
|
|
|
2246
2630
|
// The imperceptible opacity toggle forces the Android WebView
|
|
2247
2631
|
// surface to re-composite on resume, clearing the blank-but-alive
|
|
2248
2632
|
// surface bug without a reload (see runLivenessProbe).
|
|
2249
|
-
style:
|
|
2633
|
+
style: import_react_native3.Platform.OS === "android" ? [styles.webview, { opacity: 1 - androidRepaintTick % 2 * 1e-3 }] : styles.webview,
|
|
2250
2634
|
startInLoadingState: true,
|
|
2251
2635
|
injectedJavaScriptBeforeContentLoaded: getInjectedWebViewScript(),
|
|
2252
2636
|
onMessage: handleWebViewMessage,
|
|
@@ -2258,10 +2642,10 @@ function SagepilotChatProvider({
|
|
|
2258
2642
|
// iOS equivalent: WKWebView content process terminated.
|
|
2259
2643
|
onContentProcessDidTerminate: recoverNativeWebView,
|
|
2260
2644
|
renderLoading: () => (0, import_react.createElement)(
|
|
2261
|
-
|
|
2645
|
+
import_react_native3.View,
|
|
2262
2646
|
{ style: styles.loading },
|
|
2263
|
-
(0, import_react.createElement)(
|
|
2264
|
-
(0, import_react.createElement)(
|
|
2647
|
+
(0, import_react.createElement)(import_react_native3.ActivityIndicator, null),
|
|
2648
|
+
(0, import_react.createElement)(import_react_native3.Text, { style: styles.loadingText }, loadingLabel)
|
|
2265
2649
|
)
|
|
2266
2650
|
}) : null
|
|
2267
2651
|
)
|
|
@@ -2270,7 +2654,7 @@ function SagepilotChatProvider({
|
|
|
2270
2654
|
// Native attachment-source chooser. Replaces the Android Alert (capped at
|
|
2271
2655
|
// 3 buttons) so camera/library/documents all show on every platform.
|
|
2272
2656
|
(0, import_react.createElement)(
|
|
2273
|
-
|
|
2657
|
+
import_react_native3.Modal,
|
|
2274
2658
|
{
|
|
2275
2659
|
visible: sourceChooser !== null,
|
|
2276
2660
|
transparent: true,
|
|
@@ -2279,39 +2663,50 @@ function SagepilotChatProvider({
|
|
|
2279
2663
|
onRequestClose: () => setSourceChooser(null)
|
|
2280
2664
|
},
|
|
2281
2665
|
(0, import_react.createElement)(
|
|
2282
|
-
|
|
2666
|
+
import_react_native3.Pressable,
|
|
2283
2667
|
{ style: styles.sheetBackdrop, onPress: () => setSourceChooser(null) },
|
|
2284
2668
|
(0, import_react.createElement)(
|
|
2285
|
-
|
|
2669
|
+
import_react_native3.View,
|
|
2286
2670
|
{ style: styles.sheetCard },
|
|
2287
|
-
(0, import_react.createElement)(
|
|
2671
|
+
(0, import_react.createElement)(import_react_native3.View, { style: styles.sheetHandle }),
|
|
2672
|
+
(0, import_react.createElement)(import_react_native3.Text, { style: styles.sheetTitle }, "Add attachment"),
|
|
2288
2673
|
...(internalSagepilotChat.getConfig()?.filePicker?.sources ?? []).map(
|
|
2289
2674
|
(source) => (0, import_react.createElement)(
|
|
2290
|
-
|
|
2675
|
+
import_react_native3.Pressable,
|
|
2291
2676
|
{
|
|
2292
2677
|
key: source,
|
|
2293
2678
|
accessibilityRole: "button",
|
|
2294
|
-
|
|
2679
|
+
android_ripple: { color: "rgba(15, 23, 42, 0.06)" },
|
|
2680
|
+
hitSlop: 4,
|
|
2681
|
+
style: ({ pressed }) => getSheetOptionStyle(pressed),
|
|
2295
2682
|
onPress: () => handleSourceChoice(source)
|
|
2296
2683
|
},
|
|
2297
|
-
(
|
|
2684
|
+
renderFilePickerSourceIcon(source),
|
|
2685
|
+
(0, import_react.createElement)(
|
|
2686
|
+
import_react_native3.View,
|
|
2687
|
+
{ style: styles.sheetOptionText },
|
|
2688
|
+
(0, import_react.createElement)(import_react_native3.Text, { style: styles.sheetButtonText }, FILE_PICKER_SOURCE_LABELS[source]),
|
|
2689
|
+
(0, import_react.createElement)(import_react_native3.Text, { style: styles.sheetButtonDescription }, FILE_PICKER_SOURCE_DESCRIPTIONS[source])
|
|
2690
|
+
)
|
|
2298
2691
|
)
|
|
2299
2692
|
),
|
|
2300
2693
|
(0, import_react.createElement)(
|
|
2301
|
-
|
|
2694
|
+
import_react_native3.Pressable,
|
|
2302
2695
|
{
|
|
2303
2696
|
accessibilityRole: "button",
|
|
2304
|
-
|
|
2697
|
+
android_ripple: { color: "rgba(15, 23, 42, 0.06)" },
|
|
2698
|
+
style: ({ pressed }) => getSheetCancelStyle(pressed),
|
|
2305
2699
|
onPress: () => setSourceChooser(null)
|
|
2306
2700
|
},
|
|
2307
|
-
(0, import_react.createElement)(
|
|
2701
|
+
(0, import_react.createElement)(import_lucide_react_native.X, { color: "#334155", size: 18, strokeWidth: 2.5, style: styles.sheetCancelIcon }),
|
|
2702
|
+
(0, import_react.createElement)(import_react_native3.Text, { style: styles.sheetCancelText }, "Cancel")
|
|
2308
2703
|
)
|
|
2309
2704
|
)
|
|
2310
2705
|
)
|
|
2311
2706
|
)
|
|
2312
2707
|
);
|
|
2313
2708
|
}
|
|
2314
|
-
var styles =
|
|
2709
|
+
var styles = import_react_native3.StyleSheet.create({
|
|
2315
2710
|
root: {
|
|
2316
2711
|
flex: 1
|
|
2317
2712
|
},
|
|
@@ -2397,7 +2792,7 @@ var styles = import_react_native2.StyleSheet.create({
|
|
|
2397
2792
|
borderStyle: "none"
|
|
2398
2793
|
},
|
|
2399
2794
|
loading: {
|
|
2400
|
-
...
|
|
2795
|
+
...import_react_native3.StyleSheet.absoluteFillObject,
|
|
2401
2796
|
alignItems: "center",
|
|
2402
2797
|
justifyContent: "center",
|
|
2403
2798
|
backgroundColor: "#ffffff"
|
|
@@ -2409,43 +2804,109 @@ var styles = import_react_native2.StyleSheet.create({
|
|
|
2409
2804
|
},
|
|
2410
2805
|
sheetBackdrop: {
|
|
2411
2806
|
flex: 1,
|
|
2807
|
+
alignItems: "center",
|
|
2412
2808
|
justifyContent: "flex-end",
|
|
2413
|
-
backgroundColor: "rgba(
|
|
2809
|
+
backgroundColor: "rgba(15, 23, 42, 0.48)"
|
|
2414
2810
|
},
|
|
2415
2811
|
sheetCard: {
|
|
2812
|
+
width: "100%",
|
|
2813
|
+
maxWidth: 540,
|
|
2416
2814
|
backgroundColor: "#ffffff",
|
|
2417
|
-
borderTopLeftRadius:
|
|
2418
|
-
borderTopRightRadius:
|
|
2419
|
-
paddingTop:
|
|
2420
|
-
paddingBottom:
|
|
2421
|
-
paddingHorizontal:
|
|
2815
|
+
borderTopLeftRadius: 26,
|
|
2816
|
+
borderTopRightRadius: 26,
|
|
2817
|
+
paddingTop: 10,
|
|
2818
|
+
paddingBottom: 18,
|
|
2819
|
+
paddingHorizontal: 14,
|
|
2820
|
+
shadowColor: "#0f172a",
|
|
2821
|
+
shadowOpacity: 0.22,
|
|
2822
|
+
shadowRadius: 28,
|
|
2823
|
+
shadowOffset: { width: 0, height: -10 },
|
|
2824
|
+
elevation: 18
|
|
2825
|
+
},
|
|
2826
|
+
sheetHandle: {
|
|
2827
|
+
alignSelf: "center",
|
|
2828
|
+
width: 42,
|
|
2829
|
+
height: 5,
|
|
2830
|
+
borderRadius: 999,
|
|
2831
|
+
backgroundColor: "#d1d5db",
|
|
2832
|
+
marginBottom: 14
|
|
2422
2833
|
},
|
|
2423
2834
|
sheetTitle: {
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2835
|
+
color: "#111827",
|
|
2836
|
+
fontSize: 17,
|
|
2837
|
+
fontWeight: "700",
|
|
2838
|
+
paddingBottom: 12,
|
|
2839
|
+
paddingHorizontal: 4
|
|
2429
2840
|
},
|
|
2430
|
-
|
|
2431
|
-
minHeight:
|
|
2841
|
+
sheetOption: {
|
|
2842
|
+
minHeight: 70,
|
|
2843
|
+
flexDirection: "row",
|
|
2844
|
+
alignItems: "center",
|
|
2845
|
+
borderRadius: 18,
|
|
2846
|
+
paddingHorizontal: 12,
|
|
2847
|
+
marginBottom: 6,
|
|
2848
|
+
backgroundColor: "#ffffff"
|
|
2849
|
+
},
|
|
2850
|
+
sheetOptionPressed: {
|
|
2851
|
+
backgroundColor: "#f8fafc",
|
|
2852
|
+
transform: [{ scale: 0.985 }]
|
|
2853
|
+
},
|
|
2854
|
+
sheetOptionIcon: {
|
|
2855
|
+
width: 46,
|
|
2856
|
+
height: 46,
|
|
2857
|
+
borderRadius: 16,
|
|
2858
|
+
marginRight: 14,
|
|
2432
2859
|
alignItems: "center",
|
|
2433
2860
|
justifyContent: "center",
|
|
2434
|
-
|
|
2861
|
+
position: "relative",
|
|
2862
|
+
overflow: "hidden"
|
|
2863
|
+
},
|
|
2864
|
+
sheetOptionText: {
|
|
2865
|
+
flex: 1,
|
|
2866
|
+
justifyContent: "center"
|
|
2435
2867
|
},
|
|
2436
2868
|
sheetButtonText: {
|
|
2437
|
-
color: "#
|
|
2869
|
+
color: "#0f172a",
|
|
2438
2870
|
fontSize: 16,
|
|
2439
|
-
fontWeight: "
|
|
2871
|
+
fontWeight: "700"
|
|
2872
|
+
},
|
|
2873
|
+
sheetButtonDescription: {
|
|
2874
|
+
color: "#64748b",
|
|
2875
|
+
fontSize: 13,
|
|
2876
|
+
fontWeight: "500",
|
|
2877
|
+
marginTop: 3
|
|
2878
|
+
},
|
|
2879
|
+
cameraOptionIcon: {
|
|
2880
|
+
backgroundColor: "#e0f2fe"
|
|
2881
|
+
},
|
|
2882
|
+
libraryOptionIcon: {
|
|
2883
|
+
backgroundColor: "#ecfdf5"
|
|
2884
|
+
},
|
|
2885
|
+
documentsOptionIcon: {
|
|
2886
|
+
backgroundColor: "#f5f3ff"
|
|
2440
2887
|
},
|
|
2441
2888
|
sheetCancelButton: {
|
|
2442
|
-
|
|
2443
|
-
|
|
2889
|
+
minHeight: 56,
|
|
2890
|
+
flexDirection: "row",
|
|
2891
|
+
alignItems: "center",
|
|
2892
|
+
justifyContent: "center",
|
|
2893
|
+
borderRadius: 18,
|
|
2894
|
+
marginTop: 8,
|
|
2895
|
+
backgroundColor: "#f1f5f9"
|
|
2896
|
+
},
|
|
2897
|
+
sheetCancelButtonPressed: {
|
|
2898
|
+
backgroundColor: "#e2e8f0",
|
|
2899
|
+
transform: [{ scale: 0.985 }]
|
|
2900
|
+
},
|
|
2901
|
+
sheetCancelIcon: {
|
|
2902
|
+
width: 18,
|
|
2903
|
+
height: 18,
|
|
2904
|
+
marginRight: 8
|
|
2444
2905
|
},
|
|
2445
2906
|
sheetCancelText: {
|
|
2446
|
-
color: "#
|
|
2907
|
+
color: "#0f172a",
|
|
2447
2908
|
fontSize: 16,
|
|
2448
|
-
fontWeight: "
|
|
2909
|
+
fontWeight: "700"
|
|
2449
2910
|
}
|
|
2450
2911
|
});
|
|
2451
2912
|
|
|
@@ -2500,6 +2961,9 @@ function useSagepilotChat() {
|
|
|
2500
2961
|
}
|
|
2501
2962
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2502
2963
|
0 && (module.exports = {
|
|
2964
|
+
SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION,
|
|
2965
|
+
SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES,
|
|
2966
|
+
SAGEPILOT_DEFAULT_IMAGE_QUALITY,
|
|
2503
2967
|
SagepilotChat,
|
|
2504
2968
|
SagepilotChatError,
|
|
2505
2969
|
SagepilotChatProvider,
|
|
@@ -2508,5 +2972,7 @@ function useSagepilotChat() {
|
|
|
2508
2972
|
createKeychainTokenStorage,
|
|
2509
2973
|
createSagepilotFilePicker,
|
|
2510
2974
|
createSagepilotFileStore,
|
|
2975
|
+
ensureSagepilotAndroidCameraPermission,
|
|
2976
|
+
promptOpenSagepilotCameraSettings,
|
|
2511
2977
|
useSagepilotChat
|
|
2512
2978
|
});
|