@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.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/public/SdkClient.ts
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
|
|
1
4
|
// src/core/errors/SagepilotChatError.ts
|
|
2
5
|
var SagepilotChatError = class extends Error {
|
|
3
6
|
constructor(code, message, options = {}) {
|
|
@@ -15,7 +18,7 @@ var SagepilotChatError = class extends Error {
|
|
|
15
18
|
|
|
16
19
|
// src/core/config/constants.ts
|
|
17
20
|
var SDK_NAME = "@sagepilot-ai/react-native-sdk";
|
|
18
|
-
var SDK_VERSION = "0.
|
|
21
|
+
var SDK_VERSION = "0.3.0";
|
|
19
22
|
var DEFAULT_HOST = "https://app.sagepilot.ai";
|
|
20
23
|
var DEFAULT_WIDGET_HOST = "https://app.sagepilot.ai";
|
|
21
24
|
var CUSTOMER_API_PREFIX = "/customer-api/v1";
|
|
@@ -324,6 +327,34 @@ async function resolveDeviceInfo(adapter) {
|
|
|
324
327
|
return adapter.getDeviceInfo();
|
|
325
328
|
}
|
|
326
329
|
|
|
330
|
+
// src/core/storage/cache.ts
|
|
331
|
+
function createAsyncStorageCacheStorage(asyncStorage) {
|
|
332
|
+
return {
|
|
333
|
+
getItem: (key) => asyncStorage.getItem(key),
|
|
334
|
+
setItem: (key, value) => asyncStorage.setItem(key, value),
|
|
335
|
+
removeItem: (key) => asyncStorage.removeItem(key)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function createJsonCache(storage, namespace) {
|
|
339
|
+
return {
|
|
340
|
+
async get(key) {
|
|
341
|
+
const value = await storage.getItem(`${namespace}:${key}`);
|
|
342
|
+
if (!value) return null;
|
|
343
|
+
try {
|
|
344
|
+
return JSON.parse(value);
|
|
345
|
+
} catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
async set(key, value) {
|
|
350
|
+
await storage.setItem(`${namespace}:${key}`, JSON.stringify(value));
|
|
351
|
+
},
|
|
352
|
+
async remove(key) {
|
|
353
|
+
await storage.removeItem(`${namespace}:${key}`);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
327
358
|
// src/resources/channels.ts
|
|
328
359
|
function bootstrapChannel(http, host, channelId) {
|
|
329
360
|
return http.request(
|
|
@@ -407,6 +438,9 @@ function fetchUnreadCount(http, host, channelId, sessionId, authorizationHeader)
|
|
|
407
438
|
}
|
|
408
439
|
|
|
409
440
|
// src/public/SdkClient.ts
|
|
441
|
+
var PRESENTATION_CACHE_NAMESPACE = "sagepilot_rn_presentation";
|
|
442
|
+
var PRESENTATION_CACHE_KEY = "state";
|
|
443
|
+
var PRESENTATION_RESTORE_MAX_AGE_MS = 15 * 60 * 1e3;
|
|
410
444
|
var DEFAULT_MOBILE_LAUNCHER_CONFIG = {
|
|
411
445
|
label: "Chat",
|
|
412
446
|
buttonColor: "#173c2d",
|
|
@@ -476,6 +510,10 @@ function readRecordField(input, key) {
|
|
|
476
510
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
477
511
|
return value;
|
|
478
512
|
}
|
|
513
|
+
function isFreshPresentationState(updatedAt) {
|
|
514
|
+
if (typeof updatedAt !== "number" || !Number.isFinite(updatedAt)) return false;
|
|
515
|
+
return Date.now() - updatedAt <= PRESENTATION_RESTORE_MAX_AGE_MS;
|
|
516
|
+
}
|
|
479
517
|
function toPublicSessionState(session) {
|
|
480
518
|
return {
|
|
481
519
|
session_id: session.session_id,
|
|
@@ -565,6 +603,7 @@ var SagepilotReactNativeChat = class {
|
|
|
565
603
|
this.channel = channel;
|
|
566
604
|
this.session = session;
|
|
567
605
|
this.isConfigured = true;
|
|
606
|
+
await this.restorePresentationFromCache();
|
|
568
607
|
this.emitReady();
|
|
569
608
|
this.emitState();
|
|
570
609
|
if (config.behavior?.enableUnreadPolling ?? true) {
|
|
@@ -704,12 +743,14 @@ var SagepilotReactNativeChat = class {
|
|
|
704
743
|
this.hostedChatView = { screen: "home" };
|
|
705
744
|
if (this.presented) {
|
|
706
745
|
this.emitState();
|
|
746
|
+
this.persistPresentationState();
|
|
707
747
|
return true;
|
|
708
748
|
}
|
|
709
749
|
this.presented = true;
|
|
710
750
|
this.setUnreadCount(0);
|
|
711
751
|
this.presentCallbacks.forEach((callback) => callback(this.getLifecycleState()));
|
|
712
752
|
this.emitState();
|
|
753
|
+
this.persistPresentationState();
|
|
713
754
|
return true;
|
|
714
755
|
}
|
|
715
756
|
presentMessages() {
|
|
@@ -737,6 +778,7 @@ var SagepilotReactNativeChat = class {
|
|
|
737
778
|
this.emitUnreadChange();
|
|
738
779
|
this.dismissCallbacks.forEach((callback) => callback(this.getLifecycleState()));
|
|
739
780
|
this.emitState();
|
|
781
|
+
this.persistPresentationState();
|
|
740
782
|
return true;
|
|
741
783
|
}
|
|
742
784
|
hide() {
|
|
@@ -871,6 +913,10 @@ var SagepilotReactNativeChat = class {
|
|
|
871
913
|
}
|
|
872
914
|
return this.session?.conversation_id ?? void 0;
|
|
873
915
|
}
|
|
916
|
+
/** Returns the chat id that is explicitly part of the current hosted URL. */
|
|
917
|
+
getHostedRouteChatId() {
|
|
918
|
+
return this.hostedChatView.screen === "composer" ? this.hostedChatView.chatId : void 0;
|
|
919
|
+
}
|
|
874
920
|
getHostedIdentityMessage() {
|
|
875
921
|
if (!this.legacyWidgetJwt) return null;
|
|
876
922
|
return {
|
|
@@ -881,6 +927,34 @@ var SagepilotReactNativeChat = class {
|
|
|
881
927
|
}
|
|
882
928
|
};
|
|
883
929
|
}
|
|
930
|
+
/** Captures the hosted route that should receive a picked attachment batch. */
|
|
931
|
+
getHostedAttachmentTarget() {
|
|
932
|
+
return {
|
|
933
|
+
chatId: this.getActiveHostedChatId(),
|
|
934
|
+
routeChatId: this.getHostedRouteChatId(),
|
|
935
|
+
hostedChatView: this.serializeHostedChatView(this.hostedChatView)
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
/** Restores the hosted route for a queued attachment batch before delivery. */
|
|
939
|
+
restoreHostedAttachmentTarget(target) {
|
|
940
|
+
if (Platform.OS !== "android") return false;
|
|
941
|
+
if (!target) return false;
|
|
942
|
+
const hostedChatView = this.readPersistedHostedChatView(target.hostedChatView);
|
|
943
|
+
if (!hostedChatView) return false;
|
|
944
|
+
const currentChatId = this.getActiveHostedChatId();
|
|
945
|
+
const targetChatId = normalizeOptionalString(target.chatId);
|
|
946
|
+
const routeChatId = normalizeOptionalString(target.routeChatId) ?? (hostedChatView.screen === "composer" ? normalizeOptionalString(hostedChatView.chatId) : void 0);
|
|
947
|
+
const nextHostedChatView = routeChatId && hostedChatView.screen === "composer" ? this.withPinnedChatId(hostedChatView, routeChatId) : hostedChatView;
|
|
948
|
+
if (this.presented && (!targetChatId || currentChatId === targetChatId) && this.areHostedChatViewsEqual(this.hostedChatView, nextHostedChatView)) {
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
this.hostedChatView = nextHostedChatView;
|
|
952
|
+
this.presented = true;
|
|
953
|
+
this.setUnreadCount(0);
|
|
954
|
+
this.emitState();
|
|
955
|
+
this.persistPresentationState();
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
884
958
|
handleHostedBridgeMessage(message) {
|
|
885
959
|
if (!message) return false;
|
|
886
960
|
if (message.type === "close_widget") {
|
|
@@ -948,6 +1022,7 @@ var SagepilotReactNativeChat = class {
|
|
|
948
1022
|
}
|
|
949
1023
|
destroy() {
|
|
950
1024
|
this.stopUnreadPolling();
|
|
1025
|
+
this.persistPresentationState({ cleanShutdown: true, presented: false });
|
|
951
1026
|
this.resetRuntimeState();
|
|
952
1027
|
this.emitState();
|
|
953
1028
|
this.identifyCallbacks.clear();
|
|
@@ -1103,8 +1178,86 @@ var SagepilotReactNativeChat = class {
|
|
|
1103
1178
|
this.presentCallbacks.forEach((callback) => callback(this.getLifecycleState()));
|
|
1104
1179
|
}
|
|
1105
1180
|
this.emitState();
|
|
1181
|
+
this.persistPresentationState();
|
|
1106
1182
|
return true;
|
|
1107
1183
|
}
|
|
1184
|
+
/** Returns the dedicated cache used for hosted-chat presentation recovery. */
|
|
1185
|
+
getPresentationCache() {
|
|
1186
|
+
const cacheStorage = this.runtimeOptions?.cacheStorage;
|
|
1187
|
+
if (!cacheStorage) return null;
|
|
1188
|
+
return createJsonCache(cacheStorage, PRESENTATION_CACHE_NAMESPACE);
|
|
1189
|
+
}
|
|
1190
|
+
/** Removes callback-only fields so the hosted route can be safely serialized. */
|
|
1191
|
+
serializeHostedChatView(view) {
|
|
1192
|
+
if (view.screen !== "composer") return view;
|
|
1193
|
+
return {
|
|
1194
|
+
screen: "composer",
|
|
1195
|
+
message: view.message,
|
|
1196
|
+
mode: view.mode,
|
|
1197
|
+
chatId: view.chatId,
|
|
1198
|
+
metadata: view.metadata
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
/** Validates a persisted hosted view before using it to rebuild a widget URL. */
|
|
1202
|
+
readPersistedHostedChatView(view) {
|
|
1203
|
+
if (!view || typeof view !== "object") return null;
|
|
1204
|
+
const record = view;
|
|
1205
|
+
if (record.screen === "home") return { screen: "home" };
|
|
1206
|
+
if (record.screen === "messages") return { screen: "messages" };
|
|
1207
|
+
if (record.screen !== "composer") return null;
|
|
1208
|
+
const mode = record.mode === "new" ? "new" : "auto";
|
|
1209
|
+
return {
|
|
1210
|
+
screen: "composer",
|
|
1211
|
+
message: typeof record.message === "string" ? record.message : void 0,
|
|
1212
|
+
mode,
|
|
1213
|
+
chatId: typeof record.chatId === "string" && record.chatId ? record.chatId : void 0,
|
|
1214
|
+
metadata: readRecordField(record, "metadata")
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
/** Forces a persisted hosted route to target the exact chat that owns a pending attachment. */
|
|
1218
|
+
withPinnedChatId(view, chatId) {
|
|
1219
|
+
if (view.screen === "composer") {
|
|
1220
|
+
return {
|
|
1221
|
+
...view,
|
|
1222
|
+
chatId
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
screen: "composer",
|
|
1227
|
+
mode: "auto",
|
|
1228
|
+
chatId
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
/** Compares hosted route state without relying on object identity. */
|
|
1232
|
+
areHostedChatViewsEqual(first, second) {
|
|
1233
|
+
return JSON.stringify(this.serializeHostedChatView(first)) === JSON.stringify(this.serializeHostedChatView(second));
|
|
1234
|
+
}
|
|
1235
|
+
/** Persists presentation state without blocking customer-facing SDK methods. */
|
|
1236
|
+
persistPresentationState(options) {
|
|
1237
|
+
const cache = this.getPresentationCache();
|
|
1238
|
+
if (!cache) return;
|
|
1239
|
+
const state = {
|
|
1240
|
+
presented: options?.presented ?? this.presented,
|
|
1241
|
+
hostedChatView: this.serializeHostedChatView(this.hostedChatView),
|
|
1242
|
+
updatedAt: Date.now(),
|
|
1243
|
+
cleanShutdown: options?.cleanShutdown
|
|
1244
|
+
};
|
|
1245
|
+
void cache.set(PRESENTATION_CACHE_KEY, state).catch(() => void 0);
|
|
1246
|
+
}
|
|
1247
|
+
/** Restores Android presentation state after configure without resetting the hosted route to Home. */
|
|
1248
|
+
async restorePresentationFromCache() {
|
|
1249
|
+
const cache = this.getPresentationCache();
|
|
1250
|
+
if (!cache || Platform.OS !== "android") return;
|
|
1251
|
+
const persisted = await cache.get(PRESENTATION_CACHE_KEY).catch(() => null);
|
|
1252
|
+
if (persisted?.cleanShutdown) return;
|
|
1253
|
+
if (!isFreshPresentationState(persisted?.updatedAt)) return;
|
|
1254
|
+
if (!persisted?.presented) return;
|
|
1255
|
+
const hostedChatView = this.readPersistedHostedChatView(persisted.hostedChatView);
|
|
1256
|
+
if (!hostedChatView) return;
|
|
1257
|
+
this.hostedChatView = hostedChatView;
|
|
1258
|
+
this.presented = true;
|
|
1259
|
+
this.setUnreadCount(0);
|
|
1260
|
+
}
|
|
1108
1261
|
};
|
|
1109
1262
|
var internalSagepilotChat = new SagepilotReactNativeChat();
|
|
1110
1263
|
var SagepilotChat = {
|
|
@@ -1141,34 +1294,6 @@ var SagepilotChat = {
|
|
|
1141
1294
|
destroy: () => internalSagepilotChat.destroy()
|
|
1142
1295
|
};
|
|
1143
1296
|
|
|
1144
|
-
// src/core/storage/cache.ts
|
|
1145
|
-
function createAsyncStorageCacheStorage(asyncStorage) {
|
|
1146
|
-
return {
|
|
1147
|
-
getItem: (key) => asyncStorage.getItem(key),
|
|
1148
|
-
setItem: (key, value) => asyncStorage.setItem(key, value),
|
|
1149
|
-
removeItem: (key) => asyncStorage.removeItem(key)
|
|
1150
|
-
};
|
|
1151
|
-
}
|
|
1152
|
-
function createJsonCache(storage, namespace) {
|
|
1153
|
-
return {
|
|
1154
|
-
async get(key) {
|
|
1155
|
-
const value = await storage.getItem(`${namespace}:${key}`);
|
|
1156
|
-
if (!value) return null;
|
|
1157
|
-
try {
|
|
1158
|
-
return JSON.parse(value);
|
|
1159
|
-
} catch {
|
|
1160
|
-
return null;
|
|
1161
|
-
}
|
|
1162
|
-
},
|
|
1163
|
-
async set(key, value) {
|
|
1164
|
-
await storage.setItem(`${namespace}:${key}`, JSON.stringify(value));
|
|
1165
|
-
},
|
|
1166
|
-
async remove(key) {
|
|
1167
|
-
await storage.removeItem(`${namespace}:${key}`);
|
|
1168
|
-
}
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
1297
|
// src/core/storage/fileStore.ts
|
|
1173
1298
|
var DEFAULT_DIRECTORY_NAME = "sagepilot-attachments";
|
|
1174
1299
|
function sanitizeKey(key) {
|
|
@@ -1226,21 +1351,30 @@ function createSagepilotFileStore(blobUtil, options = {}) {
|
|
|
1226
1351
|
}
|
|
1227
1352
|
|
|
1228
1353
|
// src/core/native/filePicker.ts
|
|
1229
|
-
import { PermissionsAndroid, Platform } from "react-native";
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
var DEFAULT_IMAGE_MIME_TYPE = "image/jpeg";
|
|
1233
|
-
var DEFAULT_DOCUMENT_MIME_TYPE = "application/octet-stream";
|
|
1234
|
-
var DEFAULT_IMAGE_SELECTION_LIMIT = 5;
|
|
1235
|
-
var DEFAULT_DOCUMENT_MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
|
|
1236
|
-
var DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024;
|
|
1354
|
+
import { Alert, Linking, PermissionsAndroid, Platform as Platform2 } from "react-native";
|
|
1355
|
+
|
|
1356
|
+
// src/core/native/filePickerShared.ts
|
|
1237
1357
|
var SagepilotFilePickerError = class extends Error {
|
|
1358
|
+
/** Creates a typed picker error that can be surfaced over the widget bridge. */
|
|
1238
1359
|
constructor(code, message) {
|
|
1239
1360
|
super(message);
|
|
1240
1361
|
this.name = "SagepilotFilePickerError";
|
|
1241
1362
|
this.code = code;
|
|
1242
1363
|
}
|
|
1243
1364
|
};
|
|
1365
|
+
var SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION = 1920;
|
|
1366
|
+
var SAGEPILOT_DEFAULT_IMAGE_QUALITY = 0.8;
|
|
1367
|
+
var SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024;
|
|
1368
|
+
|
|
1369
|
+
// src/core/native/filePicker.ts
|
|
1370
|
+
var DEBUG_PREFIX = "[SagepilotSDK][FilePicker]";
|
|
1371
|
+
var DEFAULT_IMAGE_MAX_DIMENSION = SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION;
|
|
1372
|
+
var DEFAULT_IMAGE_QUALITY = SAGEPILOT_DEFAULT_IMAGE_QUALITY;
|
|
1373
|
+
var DEFAULT_IMAGE_MIME_TYPE = "image/jpeg";
|
|
1374
|
+
var DEFAULT_DOCUMENT_MIME_TYPE = "application/octet-stream";
|
|
1375
|
+
var DEFAULT_IMAGE_SELECTION_LIMIT = 5;
|
|
1376
|
+
var DEFAULT_DOCUMENT_MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
|
|
1377
|
+
var DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES = SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES;
|
|
1244
1378
|
function bytesToMb(bytes) {
|
|
1245
1379
|
return Math.round(bytes / (1024 * 1024) * 10) / 10;
|
|
1246
1380
|
}
|
|
@@ -1250,15 +1384,44 @@ function estimateBase64ByteSize(dataBase64) {
|
|
|
1250
1384
|
function stripFileScheme(uri) {
|
|
1251
1385
|
return uri.startsWith("file://") ? uri.replace("file://", "") : uri;
|
|
1252
1386
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1387
|
+
function debugFilePicker(message, details) {
|
|
1388
|
+
console.log(`${DEBUG_PREFIX} ${message}`, details ?? "");
|
|
1389
|
+
}
|
|
1390
|
+
async function promptOpenSagepilotCameraSettings() {
|
|
1391
|
+
if (Platform2.OS !== "android") return;
|
|
1392
|
+
await new Promise((resolve) => {
|
|
1393
|
+
Alert.alert(
|
|
1394
|
+
"Camera access is off",
|
|
1395
|
+
"Enable camera access for this app in Settings to take a photo.",
|
|
1396
|
+
[
|
|
1397
|
+
{
|
|
1398
|
+
text: "Not now",
|
|
1399
|
+
style: "cancel",
|
|
1400
|
+
onPress: resolve
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
text: "Open Settings",
|
|
1404
|
+
onPress: () => {
|
|
1405
|
+
Linking.openSettings().finally(resolve);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
]
|
|
1409
|
+
);
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
async function ensureSagepilotAndroidCameraPermission() {
|
|
1413
|
+
if (Platform2.OS !== "android") return;
|
|
1255
1414
|
let result;
|
|
1256
1415
|
try {
|
|
1416
|
+
debugFilePicker("requesting Android camera permission");
|
|
1257
1417
|
result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA);
|
|
1258
1418
|
} catch {
|
|
1419
|
+
debugFilePicker("camera permission request threw; continuing to native launch");
|
|
1259
1420
|
return;
|
|
1260
1421
|
}
|
|
1422
|
+
debugFilePicker("Android camera permission result", { result });
|
|
1261
1423
|
if (result === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
|
|
1424
|
+
await promptOpenSagepilotCameraSettings();
|
|
1262
1425
|
throw new SagepilotFilePickerError(
|
|
1263
1426
|
"permission_denied",
|
|
1264
1427
|
"Camera access is turned off. Enable it for this app in Settings to take a photo."
|
|
@@ -1271,6 +1434,9 @@ async function ensureAndroidCameraPermission() {
|
|
|
1271
1434
|
);
|
|
1272
1435
|
}
|
|
1273
1436
|
}
|
|
1437
|
+
function canUseCameraSource(imagePicker) {
|
|
1438
|
+
return Boolean(imagePicker);
|
|
1439
|
+
}
|
|
1274
1440
|
function mapImagePickerErrorCode(errorCode) {
|
|
1275
1441
|
switch (errorCode) {
|
|
1276
1442
|
case "permission":
|
|
@@ -1347,8 +1513,16 @@ function createSagepilotFilePicker(options) {
|
|
|
1347
1513
|
const documentMaxFileSizeBytes = options.documentMaxFileSizeBytes ?? DEFAULT_DOCUMENT_MAX_FILE_SIZE_BYTES;
|
|
1348
1514
|
const imageMaxFileSizeBytes = options.imageMaxFileSizeBytes ?? DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES;
|
|
1349
1515
|
const sources = [];
|
|
1350
|
-
if (imagePicker) sources.push("camera"
|
|
1516
|
+
if (canUseCameraSource(imagePicker)) sources.push("camera");
|
|
1517
|
+
if (imagePicker) sources.push("library");
|
|
1351
1518
|
if (documentsPicker) sources.push("documents");
|
|
1519
|
+
debugFilePicker("adapter created", {
|
|
1520
|
+
platform: Platform2.OS,
|
|
1521
|
+
hasImagePicker: Boolean(imagePicker),
|
|
1522
|
+
hasDocumentsPicker: Boolean(documentsPicker),
|
|
1523
|
+
hasFileReader: Boolean(fileReader),
|
|
1524
|
+
sources
|
|
1525
|
+
});
|
|
1352
1526
|
if (sources.length === 0) return void 0;
|
|
1353
1527
|
const imageOptions = {
|
|
1354
1528
|
mediaType: "photo",
|
|
@@ -1359,12 +1533,22 @@ function createSagepilotFilePicker(options) {
|
|
|
1359
1533
|
saveToPhotos: false
|
|
1360
1534
|
};
|
|
1361
1535
|
async function pickFromCamera() {
|
|
1362
|
-
|
|
1363
|
-
|
|
1536
|
+
debugFilePicker("camera pick requested", {
|
|
1537
|
+
platform: Platform2.OS,
|
|
1538
|
+
imageMaxDimension,
|
|
1539
|
+
imageQuality,
|
|
1540
|
+
imageMaxFileSizeBytes
|
|
1541
|
+
});
|
|
1542
|
+
await ensureSagepilotAndroidCameraPermission();
|
|
1543
|
+
if (!imagePicker) {
|
|
1544
|
+
throw new SagepilotFilePickerError("camera_unavailable", "The camera picker is not available in this build.");
|
|
1545
|
+
}
|
|
1546
|
+
debugFilePicker("launching image-picker camera", { platform: Platform2.OS });
|
|
1364
1547
|
return imageAssetsToPickedFiles(await imagePicker.launchCamera(imageOptions), imageMaxFileSizeBytes);
|
|
1365
1548
|
}
|
|
1366
1549
|
async function pickFromLibrary(multiple) {
|
|
1367
1550
|
if (!imagePicker) return [];
|
|
1551
|
+
debugFilePicker("library pick requested", { multiple, imageSelectionLimit });
|
|
1368
1552
|
return imageAssetsToPickedFiles(await imagePicker.launchImageLibrary({
|
|
1369
1553
|
...imageOptions,
|
|
1370
1554
|
// Bounded multi-select: 0 (unlimited) lets a huge batch OOM the bridge.
|
|
@@ -1373,6 +1557,7 @@ function createSagepilotFilePicker(options) {
|
|
|
1373
1557
|
}
|
|
1374
1558
|
async function pickDocuments(multiple) {
|
|
1375
1559
|
if (!documentsPicker) return [];
|
|
1560
|
+
debugFilePicker("document pick requested", { multiple, documentMaxFileSizeBytes });
|
|
1376
1561
|
let picked;
|
|
1377
1562
|
try {
|
|
1378
1563
|
picked = await documentsPicker.pick({ allowMultiSelection: multiple });
|
|
@@ -1422,6 +1607,7 @@ function createSagepilotFilePicker(options) {
|
|
|
1422
1607
|
return {
|
|
1423
1608
|
sources,
|
|
1424
1609
|
async pickFiles(request) {
|
|
1610
|
+
debugFilePicker("pickFiles request", request);
|
|
1425
1611
|
if (request.source === "camera") return pickFromCamera();
|
|
1426
1612
|
if (request.source === "library") return pickFromLibrary(request.multiple);
|
|
1427
1613
|
return pickDocuments(request.multiple);
|
|
@@ -1431,13 +1617,14 @@ function createSagepilotFilePicker(options) {
|
|
|
1431
1617
|
|
|
1432
1618
|
// src/ui/SagepilotChatProvider.ts
|
|
1433
1619
|
import { createElement, useCallback, useEffect, useRef, useState } from "react";
|
|
1620
|
+
import { Camera, FileText, Images, X } from "lucide-react-native";
|
|
1434
1621
|
import {
|
|
1435
1622
|
ActivityIndicator,
|
|
1436
1623
|
AppState,
|
|
1437
1624
|
KeyboardAvoidingView,
|
|
1438
|
-
Linking,
|
|
1625
|
+
Linking as Linking2,
|
|
1439
1626
|
Modal,
|
|
1440
|
-
Platform as
|
|
1627
|
+
Platform as Platform3,
|
|
1441
1628
|
Pressable,
|
|
1442
1629
|
SafeAreaView,
|
|
1443
1630
|
StyleSheet,
|
|
@@ -1448,8 +1635,11 @@ import { WebView } from "react-native-webview";
|
|
|
1448
1635
|
|
|
1449
1636
|
// src/core/webview/mobileBridge.ts
|
|
1450
1637
|
var FILE_PICKER_PROTOCOL_VERSION = 2;
|
|
1638
|
+
function buildBridgeCapabilitiesPayload(capabilities) {
|
|
1639
|
+
return { ...capabilities, filePickerProtocol: FILE_PICKER_PROTOCOL_VERSION };
|
|
1640
|
+
}
|
|
1451
1641
|
function buildBridgeCapabilitiesScript(capabilities) {
|
|
1452
|
-
const payload =
|
|
1642
|
+
const payload = buildBridgeCapabilitiesPayload(capabilities);
|
|
1453
1643
|
return [
|
|
1454
1644
|
"(function(){",
|
|
1455
1645
|
"try {",
|
|
@@ -1498,9 +1688,23 @@ function parseHostedBridgeMessage(rawData) {
|
|
|
1498
1688
|
return null;
|
|
1499
1689
|
}
|
|
1500
1690
|
}
|
|
1501
|
-
|
|
1691
|
+
function buildMobileWebViewBridgeScript(capabilities) {
|
|
1692
|
+
const payload = buildBridgeCapabilitiesPayload(capabilities);
|
|
1693
|
+
return `
|
|
1502
1694
|
(function () {
|
|
1503
|
-
|
|
1695
|
+
var bridgeCapabilities = ${JSON.stringify(payload)};
|
|
1696
|
+
var applyBridgeCapabilities = function () {
|
|
1697
|
+
try {
|
|
1698
|
+
if (window.SagepilotMobileBridge) {
|
|
1699
|
+
window.SagepilotMobileBridge.capabilities = Object.assign({}, window.SagepilotMobileBridge.capabilities, bridgeCapabilities);
|
|
1700
|
+
}
|
|
1701
|
+
} catch (error) {}
|
|
1702
|
+
};
|
|
1703
|
+
|
|
1704
|
+
if (window.__sagepilotRnBridgeInstalled) {
|
|
1705
|
+
applyBridgeCapabilities();
|
|
1706
|
+
return true;
|
|
1707
|
+
}
|
|
1504
1708
|
window.__sagepilotRnBridgeInstalled = true;
|
|
1505
1709
|
|
|
1506
1710
|
var ensureViewport = function () {
|
|
@@ -1541,9 +1745,11 @@ var mobileWebViewBridgeScript = `
|
|
|
1541
1745
|
};
|
|
1542
1746
|
|
|
1543
1747
|
window.SagepilotMobileBridge = {
|
|
1748
|
+
capabilities: bridgeCapabilities,
|
|
1544
1749
|
postMessage: sendToReactNative,
|
|
1545
1750
|
ready: function () { sendToReactNative({ type: "sagepilot:ready" }); }
|
|
1546
1751
|
};
|
|
1752
|
+
applyBridgeCapabilities();
|
|
1547
1753
|
|
|
1548
1754
|
document.addEventListener("click", function (event) {
|
|
1549
1755
|
try {
|
|
@@ -1584,6 +1790,7 @@ var mobileWebViewBridgeScript = `
|
|
|
1584
1790
|
return true;
|
|
1585
1791
|
})();
|
|
1586
1792
|
`;
|
|
1793
|
+
}
|
|
1587
1794
|
|
|
1588
1795
|
// src/specs/SagepilotInsetsViewNativeComponent.ts
|
|
1589
1796
|
import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent";
|
|
@@ -1600,13 +1807,17 @@ function readPresentationState() {
|
|
|
1600
1807
|
presentation: internalSagepilotChat.getConfig()?.presentation
|
|
1601
1808
|
};
|
|
1602
1809
|
}
|
|
1810
|
+
function getBridgeCapabilities() {
|
|
1811
|
+
return {
|
|
1812
|
+
nativeFilePicker: Boolean(internalSagepilotChat.getConfig()?.filePicker)
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1603
1815
|
function getInjectedWebViewScript() {
|
|
1816
|
+
const capabilities = getBridgeCapabilities();
|
|
1604
1817
|
return [
|
|
1605
1818
|
internalSagepilotChat.getHostedAuthScript(),
|
|
1606
|
-
|
|
1607
|
-
buildBridgeCapabilitiesScript(
|
|
1608
|
-
nativeFilePicker: Boolean(internalSagepilotChat.getConfig()?.filePicker)
|
|
1609
|
-
})
|
|
1819
|
+
buildMobileWebViewBridgeScript(capabilities),
|
|
1820
|
+
buildBridgeCapabilitiesScript(capabilities)
|
|
1610
1821
|
].filter(Boolean).join("\n");
|
|
1611
1822
|
}
|
|
1612
1823
|
var FILE_PICKER_SOURCE_LABELS = {
|
|
@@ -1614,6 +1825,22 @@ var FILE_PICKER_SOURCE_LABELS = {
|
|
|
1614
1825
|
library: "Choose from gallery",
|
|
1615
1826
|
documents: "Browse files"
|
|
1616
1827
|
};
|
|
1828
|
+
var FILE_PICKER_SOURCE_DESCRIPTIONS = {
|
|
1829
|
+
camera: "Use the in-app camera",
|
|
1830
|
+
library: "Photos and videos on this device",
|
|
1831
|
+
documents: "PDFs, documents, and Drive files"
|
|
1832
|
+
};
|
|
1833
|
+
var FILE_PICKER_SOURCE_ICONS = {
|
|
1834
|
+
camera: Camera,
|
|
1835
|
+
library: Images,
|
|
1836
|
+
documents: FileText
|
|
1837
|
+
};
|
|
1838
|
+
var FILE_PICKER_SOURCE_ICON_COLORS = {
|
|
1839
|
+
camera: "#0284c7",
|
|
1840
|
+
library: "#16a34a",
|
|
1841
|
+
documents: "#7c3aed"
|
|
1842
|
+
};
|
|
1843
|
+
var DEBUG_PREFIX2 = "[SagepilotSDK][Provider]";
|
|
1617
1844
|
var DELIVERY_RETRY_MS = 1500;
|
|
1618
1845
|
var DELIVERY_LEGACY_FALLBACK_ATTEMPTS = 2;
|
|
1619
1846
|
var DELIVERY_MAX_ATTEMPTS = 8;
|
|
@@ -1623,6 +1850,7 @@ var PENDING_FILES_CACHE_KEY = "pending_batch";
|
|
|
1623
1850
|
var LIVENESS_HEARTBEAT_STALE_MS = 4e3;
|
|
1624
1851
|
var LIVENESS_PONG_TIMEOUT_MS = 1500;
|
|
1625
1852
|
var LIVENESS_MAX_PING_ATTEMPTS = 2;
|
|
1853
|
+
var LIVENESS_PICKER_GRACE_MS = 8e3;
|
|
1626
1854
|
var LIVENESS_MAX_REMOUNTS = 2;
|
|
1627
1855
|
var batchSequence = 0;
|
|
1628
1856
|
function nextBatchId() {
|
|
@@ -1636,6 +1864,29 @@ function totalBase64Bytes(files) {
|
|
|
1636
1864
|
function storageKeyFor(batchId, index) {
|
|
1637
1865
|
return `${batchId}_${index}.bin`;
|
|
1638
1866
|
}
|
|
1867
|
+
function renderFilePickerSourceIcon(source) {
|
|
1868
|
+
const Icon = FILE_PICKER_SOURCE_ICONS[source];
|
|
1869
|
+
return createElement(
|
|
1870
|
+
View,
|
|
1871
|
+
{ style: getFilePickerSourceIconStyle(source) },
|
|
1872
|
+
createElement(Icon, {
|
|
1873
|
+
color: FILE_PICKER_SOURCE_ICON_COLORS[source],
|
|
1874
|
+
size: 23,
|
|
1875
|
+
strokeWidth: 2.35
|
|
1876
|
+
})
|
|
1877
|
+
);
|
|
1878
|
+
}
|
|
1879
|
+
function getFilePickerSourceIconStyle(source) {
|
|
1880
|
+
if (source === "camera") return [styles.sheetOptionIcon, styles.cameraOptionIcon];
|
|
1881
|
+
if (source === "library") return [styles.sheetOptionIcon, styles.libraryOptionIcon];
|
|
1882
|
+
return [styles.sheetOptionIcon, styles.documentsOptionIcon];
|
|
1883
|
+
}
|
|
1884
|
+
function getSheetOptionStyle(pressed) {
|
|
1885
|
+
return [styles.sheetOption, pressed && styles.sheetOptionPressed];
|
|
1886
|
+
}
|
|
1887
|
+
function getSheetCancelStyle(pressed) {
|
|
1888
|
+
return [styles.sheetCancelButton, pressed && styles.sheetCancelButtonPressed];
|
|
1889
|
+
}
|
|
1639
1890
|
function buildDispatchScript(messageLiteral) {
|
|
1640
1891
|
return [
|
|
1641
1892
|
"(function(){",
|
|
@@ -1647,8 +1898,14 @@ function buildDispatchScript(messageLiteral) {
|
|
|
1647
1898
|
"})();"
|
|
1648
1899
|
].join("\n");
|
|
1649
1900
|
}
|
|
1650
|
-
function buildFilesPickedScript(
|
|
1651
|
-
return buildDispatchScript(
|
|
1901
|
+
function buildFilesPickedScript(batch) {
|
|
1902
|
+
return buildDispatchScript(JSON.stringify({
|
|
1903
|
+
type: "sagepilot:files_picked",
|
|
1904
|
+
batch_id: batch.batchId,
|
|
1905
|
+
chat_id: batch.target?.chatId,
|
|
1906
|
+
conversation_id: batch.target?.chatId,
|
|
1907
|
+
files: batch.files
|
|
1908
|
+
}));
|
|
1652
1909
|
}
|
|
1653
1910
|
function buildFilePickerErrorScript(message, code) {
|
|
1654
1911
|
return buildDispatchScript(
|
|
@@ -1667,6 +1924,16 @@ function readFilePickerError(error) {
|
|
|
1667
1924
|
}
|
|
1668
1925
|
return { message: "Could not attach the selected file." };
|
|
1669
1926
|
}
|
|
1927
|
+
function debugProvider(message, details) {
|
|
1928
|
+
console.log(`${DEBUG_PREFIX2} ${message}`, details ?? "");
|
|
1929
|
+
}
|
|
1930
|
+
function describeAttachmentTarget(target) {
|
|
1931
|
+
return {
|
|
1932
|
+
targetChatId: target?.chatId,
|
|
1933
|
+
targetRouteChatId: target?.routeChatId,
|
|
1934
|
+
targetScreen: target?.hostedChatView.screen
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1670
1937
|
function getHostedIdentityDispatchScript() {
|
|
1671
1938
|
const message = internalSagepilotChat.getHostedIdentityMessage();
|
|
1672
1939
|
if (!message) return "";
|
|
@@ -1695,7 +1962,7 @@ function isInternalWebViewScheme(url) {
|
|
|
1695
1962
|
var hostedChatWebViewProps = {
|
|
1696
1963
|
allowFileAccess: true,
|
|
1697
1964
|
allowFileAccessFromFileURLs: true,
|
|
1698
|
-
...
|
|
1965
|
+
...Platform3.OS === "ios" ? {
|
|
1699
1966
|
automaticallyAdjustContentInsets: false,
|
|
1700
1967
|
contentInsetAdjustmentBehavior: "never",
|
|
1701
1968
|
hideKeyboardAccessoryView: true
|
|
@@ -1710,7 +1977,7 @@ var hostedChatWebViewProps = {
|
|
|
1710
1977
|
sharedCookiesEnabled: true,
|
|
1711
1978
|
thirdPartyCookiesEnabled: true
|
|
1712
1979
|
};
|
|
1713
|
-
var AndroidInsetsView =
|
|
1980
|
+
var AndroidInsetsView = Platform3.OS === "android" ? SagepilotInsetsViewNativeComponent_default : View;
|
|
1714
1981
|
var emptyAndroidInsets = { top: 0, bottom: 0 };
|
|
1715
1982
|
function SagepilotChatProvider({
|
|
1716
1983
|
children,
|
|
@@ -1729,6 +1996,9 @@ function SagepilotChatProvider({
|
|
|
1729
1996
|
const deliveryAttemptsRef = useRef(0);
|
|
1730
1997
|
const pendingPingRef = useRef(null);
|
|
1731
1998
|
const pingTimeoutRef = useRef(null);
|
|
1999
|
+
const livenessResumeTimerRef = useRef(null);
|
|
2000
|
+
const livenessPausedUntilRef = useRef(0);
|
|
2001
|
+
const pickerInFlightRef = useRef(false);
|
|
1732
2002
|
const livenessRemountCountRef = useRef(0);
|
|
1733
2003
|
const appStateRef = useRef(AppState.currentState);
|
|
1734
2004
|
const didReconcileRef = useRef(false);
|
|
@@ -1751,6 +2021,7 @@ function SagepilotChatProvider({
|
|
|
1751
2021
|
if (hasFileStore) {
|
|
1752
2022
|
const manifest = queue.filter((batch) => batch.storageKeys && batch.storageKeys.length === batch.files.length).map((batch) => ({
|
|
1753
2023
|
batchId: batch.batchId,
|
|
2024
|
+
target: batch.target,
|
|
1754
2025
|
files: batch.files.map((file, index) => ({
|
|
1755
2026
|
file_name: file.file_name,
|
|
1756
2027
|
mime_type: file.mime_type,
|
|
@@ -1769,6 +2040,7 @@ function SagepilotChatProvider({
|
|
|
1769
2040
|
if (totalBytes <= PERSIST_MAX_TOTAL_BYTES) {
|
|
1770
2041
|
const manifest = queue.map((batch) => ({
|
|
1771
2042
|
batchId: batch.batchId,
|
|
2043
|
+
target: batch.target,
|
|
1772
2044
|
files: batch.files.map((file) => ({
|
|
1773
2045
|
file_name: file.file_name,
|
|
1774
2046
|
mime_type: file.mime_type,
|
|
@@ -1810,12 +2082,28 @@ function SagepilotChatProvider({
|
|
|
1810
2082
|
}
|
|
1811
2083
|
const batch = pendingBatchesRef.current[0];
|
|
1812
2084
|
if (!batch) return;
|
|
2085
|
+
if (internalSagepilotChat.restoreHostedAttachmentTarget(batch.target)) {
|
|
2086
|
+
debugProvider("restored hosted attachment target", {
|
|
2087
|
+
batchId: batch.batchId,
|
|
2088
|
+
...describeAttachmentTarget(batch.target)
|
|
2089
|
+
});
|
|
2090
|
+
widgetReadyRef.current = false;
|
|
2091
|
+
deliveryTimerRef.current = setTimeout(() => pumpDelivery(), DELIVERY_RETRY_MS);
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
1813
2094
|
deliveryAttemptsRef.current += 1;
|
|
1814
2095
|
const attempts = deliveryAttemptsRef.current;
|
|
1815
2096
|
const ref = nativeWebViewRef.current;
|
|
1816
2097
|
const shouldDeliver = ref && (widgetReadyRef.current || attempts >= DELIVERY_LEGACY_FALLBACK_ATTEMPTS);
|
|
1817
2098
|
if (shouldDeliver && ref) {
|
|
1818
|
-
|
|
2099
|
+
debugProvider("delivering native picked files", {
|
|
2100
|
+
batchId: batch.batchId,
|
|
2101
|
+
attempt: attempts,
|
|
2102
|
+
widgetReady: widgetReadyRef.current,
|
|
2103
|
+
count: batch.files.length,
|
|
2104
|
+
...describeAttachmentTarget(batch.target)
|
|
2105
|
+
});
|
|
2106
|
+
ref.injectJavaScript(buildFilesPickedScript(batch));
|
|
1819
2107
|
}
|
|
1820
2108
|
if (attempts < DELIVERY_MAX_ATTEMPTS) {
|
|
1821
2109
|
deliveryTimerRef.current = setTimeout(() => pumpDelivery(), DELIVERY_RETRY_MS);
|
|
@@ -1846,23 +2134,65 @@ function SagepilotChatProvider({
|
|
|
1846
2134
|
const batchId = nextBatchId();
|
|
1847
2135
|
const hasFileStore = Boolean(internalSagepilotChat.getConfig()?.fileStore);
|
|
1848
2136
|
const storageKeys = hasFileStore ? files.map((_, index) => storageKeyFor(batchId, index)) : void 0;
|
|
1849
|
-
const batch = {
|
|
2137
|
+
const batch = {
|
|
2138
|
+
batchId,
|
|
2139
|
+
files,
|
|
2140
|
+
storageKeys,
|
|
2141
|
+
target: internalSagepilotChat.getHostedAttachmentTarget()
|
|
2142
|
+
};
|
|
2143
|
+
debugProvider("queued native picked files", {
|
|
2144
|
+
batchId,
|
|
2145
|
+
count: files.length,
|
|
2146
|
+
totalBase64Bytes: totalBase64Bytes(files),
|
|
2147
|
+
persistedToFileStore: hasFileStore,
|
|
2148
|
+
...describeAttachmentTarget(batch.target)
|
|
2149
|
+
});
|
|
1850
2150
|
pendingBatchesRef.current = [...pendingBatchesRef.current, batch];
|
|
1851
2151
|
ensureDelivery();
|
|
1852
2152
|
writeManifest();
|
|
1853
2153
|
void writeBatchBytes(batch);
|
|
1854
2154
|
}, [ensureDelivery, writeManifest, writeBatchBytes]);
|
|
1855
|
-
const recoverNativeWebView = useCallback(() => {
|
|
2155
|
+
const recoverNativeWebView = useCallback((reasonOrEvent) => {
|
|
2156
|
+
const reason = typeof reasonOrEvent === "string" ? reasonOrEvent : "webview_render_process";
|
|
2157
|
+
debugProvider("recovering native WebView", { reason });
|
|
1856
2158
|
widgetReadyRef.current = false;
|
|
1857
2159
|
setNativeWebViewKey((key) => key + 1);
|
|
1858
2160
|
}, []);
|
|
1859
2161
|
const recoverPreloadWebView = useCallback(() => {
|
|
1860
2162
|
setPreloadWebViewKey((key) => key + 1);
|
|
1861
2163
|
}, []);
|
|
2164
|
+
const pauseLivenessAfterPicker = useCallback((durationMs) => {
|
|
2165
|
+
if (Platform3.OS !== "android") return;
|
|
2166
|
+
livenessPausedUntilRef.current = Math.max(livenessPausedUntilRef.current, Date.now() + durationMs);
|
|
2167
|
+
}, []);
|
|
1862
2168
|
const runNativeFilePicker = useCallback((source, multiple) => {
|
|
1863
2169
|
const filePicker = internalSagepilotChat.getConfig()?.filePicker;
|
|
1864
|
-
if (!filePicker)
|
|
2170
|
+
if (!filePicker) {
|
|
2171
|
+
debugProvider("native file picker requested but no adapter is configured", { source, multiple });
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
if (pickerInFlightRef.current) {
|
|
2175
|
+
debugProvider("native file picker ignored because another picker is in flight", { source, multiple });
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
debugProvider("native file picker starting", {
|
|
2179
|
+
source,
|
|
2180
|
+
multiple,
|
|
2181
|
+
configuredSources: filePicker.sources
|
|
2182
|
+
});
|
|
2183
|
+
pauseLivenessAfterPicker(6e4);
|
|
2184
|
+
pickerInFlightRef.current = true;
|
|
1865
2185
|
filePicker.pickFiles({ source, multiple }).then((files) => {
|
|
2186
|
+
debugProvider("native file picker resolved", {
|
|
2187
|
+
source,
|
|
2188
|
+
count: files.length,
|
|
2189
|
+
files: files.map((file) => ({
|
|
2190
|
+
fileName: file.file_name,
|
|
2191
|
+
mimeType: file.mime_type,
|
|
2192
|
+
size: file.size,
|
|
2193
|
+
base64Length: file.data_base64.length
|
|
2194
|
+
}))
|
|
2195
|
+
});
|
|
1866
2196
|
if (files.length === 0) {
|
|
1867
2197
|
nativeWebViewRef.current?.injectJavaScript(buildFilePickerCancelledScript());
|
|
1868
2198
|
return;
|
|
@@ -1870,14 +2200,26 @@ function SagepilotChatProvider({
|
|
|
1870
2200
|
queuePickedFiles(files);
|
|
1871
2201
|
}).catch((error) => {
|
|
1872
2202
|
const { message, code } = readFilePickerError(error);
|
|
2203
|
+
debugProvider("native file picker failed", { source, code, message });
|
|
1873
2204
|
nativeWebViewRef.current?.injectJavaScript(buildFilePickerErrorScript(message, code));
|
|
2205
|
+
}).finally(() => {
|
|
2206
|
+
pickerInFlightRef.current = false;
|
|
2207
|
+
pauseLivenessAfterPicker(LIVENESS_PICKER_GRACE_MS);
|
|
1874
2208
|
});
|
|
1875
|
-
}, [queuePickedFiles]);
|
|
2209
|
+
}, [pauseLivenessAfterPicker, queuePickedFiles]);
|
|
1876
2210
|
const openNativeFilePicker = useCallback((multiple) => {
|
|
1877
2211
|
const filePicker = internalSagepilotChat.getConfig()?.filePicker;
|
|
1878
|
-
if (!filePicker || filePicker.sources.length === 0)
|
|
2212
|
+
if (!filePicker || filePicker.sources.length === 0) {
|
|
2213
|
+
debugProvider("open native file picker ignored", {
|
|
2214
|
+
multiple,
|
|
2215
|
+
hasAdapter: Boolean(filePicker),
|
|
2216
|
+
sources: filePicker?.sources ?? []
|
|
2217
|
+
});
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
debugProvider("open native file picker", { multiple, sources: filePicker.sources });
|
|
1879
2221
|
const [onlySource] = filePicker.sources;
|
|
1880
|
-
if (filePicker.sources.length === 1 && onlySource) {
|
|
2222
|
+
if (filePicker.sources.length === 1 && onlySource && onlySource !== "camera") {
|
|
1881
2223
|
runNativeFilePicker(onlySource, multiple);
|
|
1882
2224
|
return;
|
|
1883
2225
|
}
|
|
@@ -1930,7 +2272,8 @@ function SagepilotChatProvider({
|
|
|
1930
2272
|
restored.push({
|
|
1931
2273
|
batchId: batch.batchId,
|
|
1932
2274
|
files,
|
|
1933
|
-
storageKeys: storageKeys.length === files.length ? storageKeys : void 0
|
|
2275
|
+
storageKeys: storageKeys.length === files.length ? storageKeys : void 0,
|
|
2276
|
+
target: batch.target
|
|
1934
2277
|
});
|
|
1935
2278
|
}
|
|
1936
2279
|
}
|
|
@@ -1951,6 +2294,19 @@ function SagepilotChatProvider({
|
|
|
1951
2294
|
cancelled = true;
|
|
1952
2295
|
};
|
|
1953
2296
|
}, [getPendingCache, startDelivery, writeManifest]);
|
|
2297
|
+
const clearPing = useCallback(() => {
|
|
2298
|
+
pendingPingRef.current = null;
|
|
2299
|
+
if (pingTimeoutRef.current) {
|
|
2300
|
+
clearTimeout(pingTimeoutRef.current);
|
|
2301
|
+
pingTimeoutRef.current = null;
|
|
2302
|
+
}
|
|
2303
|
+
}, []);
|
|
2304
|
+
const clearDeferredLivenessProbe = useCallback(() => {
|
|
2305
|
+
if (livenessResumeTimerRef.current) {
|
|
2306
|
+
clearTimeout(livenessResumeTimerRef.current);
|
|
2307
|
+
livenessResumeTimerRef.current = null;
|
|
2308
|
+
}
|
|
2309
|
+
}, []);
|
|
1954
2310
|
useEffect(() => {
|
|
1955
2311
|
return () => {
|
|
1956
2312
|
if (deliveryTimerRef.current) {
|
|
@@ -1961,19 +2317,27 @@ function SagepilotChatProvider({
|
|
|
1961
2317
|
clearTimeout(pingTimeoutRef.current);
|
|
1962
2318
|
pingTimeoutRef.current = null;
|
|
1963
2319
|
}
|
|
2320
|
+
clearDeferredLivenessProbe();
|
|
1964
2321
|
};
|
|
1965
|
-
}, []);
|
|
1966
|
-
const clearPing = useCallback(() => {
|
|
1967
|
-
pendingPingRef.current = null;
|
|
1968
|
-
if (pingTimeoutRef.current) {
|
|
1969
|
-
clearTimeout(pingTimeoutRef.current);
|
|
1970
|
-
pingTimeoutRef.current = null;
|
|
1971
|
-
}
|
|
1972
|
-
}, []);
|
|
2322
|
+
}, [clearDeferredLivenessProbe]);
|
|
1973
2323
|
const runLivenessProbe = useCallback(() => {
|
|
1974
|
-
if (
|
|
2324
|
+
if (Platform3.OS !== "android") return;
|
|
1975
2325
|
const ref = nativeWebViewRef.current;
|
|
1976
2326
|
if (!ref || !internalSagepilotChat.isPresented()) return;
|
|
2327
|
+
const pauseRemainingMs = livenessPausedUntilRef.current - Date.now();
|
|
2328
|
+
if (pickerInFlightRef.current || pauseRemainingMs > 0) {
|
|
2329
|
+
const retryDelayMs = pickerInFlightRef.current ? 500 : Math.max(0, pauseRemainingMs);
|
|
2330
|
+
debugProvider("liveness probe deferred after picker", {
|
|
2331
|
+
pickerInFlight: pickerInFlightRef.current,
|
|
2332
|
+
retryDelayMs
|
|
2333
|
+
});
|
|
2334
|
+
clearDeferredLivenessProbe();
|
|
2335
|
+
livenessResumeTimerRef.current = setTimeout(() => {
|
|
2336
|
+
livenessResumeTimerRef.current = null;
|
|
2337
|
+
runLivenessProbe();
|
|
2338
|
+
}, retryDelayMs);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
1977
2341
|
setAndroidRepaintTick((tick) => tick + 1);
|
|
1978
2342
|
const attempts = (pendingPingRef.current?.attempts ?? 0) + 1;
|
|
1979
2343
|
const nonce = `${Date.now().toString(36)}_${attempts}`;
|
|
@@ -1983,12 +2347,12 @@ function SagepilotChatProvider({
|
|
|
1983
2347
|
pingTimeoutRef.current = setTimeout(() => {
|
|
1984
2348
|
if (attempts >= LIVENESS_MAX_PING_ATTEMPTS) {
|
|
1985
2349
|
clearPing();
|
|
1986
|
-
recoverNativeWebView();
|
|
2350
|
+
recoverNativeWebView("liveness_timeout");
|
|
1987
2351
|
return;
|
|
1988
2352
|
}
|
|
1989
2353
|
runLivenessProbe();
|
|
1990
2354
|
}, LIVENESS_PONG_TIMEOUT_MS);
|
|
1991
|
-
}, [clearPing, recoverNativeWebView]);
|
|
2355
|
+
}, [clearDeferredLivenessProbe, clearPing, recoverNativeWebView]);
|
|
1992
2356
|
useEffect(() => {
|
|
1993
2357
|
const subscription = AppState.addEventListener("change", (nextState) => {
|
|
1994
2358
|
const prev = appStateRef.current;
|
|
@@ -2018,11 +2382,11 @@ function SagepilotChatProvider({
|
|
|
2018
2382
|
const presentationStyle = state.presentation?.style ?? "sheet";
|
|
2019
2383
|
const isFullScreenModal = presentationStyle === "fullScreen";
|
|
2020
2384
|
const animationType = presentationStyle === "fullScreen" || presentationStyle === "push" ? "slide" : "fade";
|
|
2021
|
-
const ModalContainer =
|
|
2022
|
-
const NativeModalContainer =
|
|
2023
|
-
const ChatContentContainer =
|
|
2024
|
-
const nativeModalContainerProps =
|
|
2025
|
-
const chatContentContainerProps =
|
|
2385
|
+
const ModalContainer = Platform3.OS === "ios" && isFullScreenModal ? SafeAreaView : View;
|
|
2386
|
+
const NativeModalContainer = Platform3.OS === "android" ? AndroidInsetsView : ModalContainer;
|
|
2387
|
+
const ChatContentContainer = Platform3.OS === "ios" ? KeyboardAvoidingView : View;
|
|
2388
|
+
const nativeModalContainerProps = Platform3.OS === "android" ? { style: styles.container, onInsetsChange: handleAndroidInsetsChange } : { style: styles.container };
|
|
2389
|
+
const chatContentContainerProps = Platform3.OS === "ios" ? {
|
|
2026
2390
|
behavior: "padding",
|
|
2027
2391
|
enabled: true,
|
|
2028
2392
|
keyboardVerticalOffset: 0,
|
|
@@ -2030,7 +2394,7 @@ function SagepilotChatProvider({
|
|
|
2030
2394
|
} : {
|
|
2031
2395
|
style: [
|
|
2032
2396
|
styles.modalContent,
|
|
2033
|
-
|
|
2397
|
+
Platform3.OS === "android" ? {
|
|
2034
2398
|
paddingTop: androidModalInsets.top,
|
|
2035
2399
|
paddingBottom: androidModalInsets.bottom
|
|
2036
2400
|
} : null
|
|
@@ -2051,6 +2415,11 @@ function SagepilotChatProvider({
|
|
|
2051
2415
|
const ackBatchId = message.batch_id;
|
|
2052
2416
|
const head = pendingBatchesRef.current[0];
|
|
2053
2417
|
if (head && (!ackBatchId || ackBatchId === head.batchId)) {
|
|
2418
|
+
debugProvider("native picked files acknowledged", {
|
|
2419
|
+
batchId: head.batchId,
|
|
2420
|
+
ackBatchId,
|
|
2421
|
+
count: message.count
|
|
2422
|
+
});
|
|
2054
2423
|
acknowledgeHeadBatch();
|
|
2055
2424
|
}
|
|
2056
2425
|
return;
|
|
@@ -2060,9 +2429,13 @@ function SagepilotChatProvider({
|
|
|
2060
2429
|
if (pending && (!message.nonce || message.nonce === pending.nonce)) {
|
|
2061
2430
|
if (message.alive === false) {
|
|
2062
2431
|
clearPing();
|
|
2432
|
+
debugProvider("liveness probe reported dead widget", {
|
|
2433
|
+
supportsHeartbeat: message.supportsHeartbeat,
|
|
2434
|
+
remountCount: livenessRemountCountRef.current
|
|
2435
|
+
});
|
|
2063
2436
|
if (livenessRemountCountRef.current < LIVENESS_MAX_REMOUNTS) {
|
|
2064
2437
|
livenessRemountCountRef.current += 1;
|
|
2065
|
-
recoverNativeWebView();
|
|
2438
|
+
recoverNativeWebView("liveness_dead_pong");
|
|
2066
2439
|
}
|
|
2067
2440
|
} else {
|
|
2068
2441
|
livenessRemountCountRef.current = 0;
|
|
@@ -2084,7 +2457,13 @@ function SagepilotChatProvider({
|
|
|
2084
2457
|
if (!script || !nativeWebViewRef.current) return;
|
|
2085
2458
|
nativeWebViewRef.current.injectJavaScript(script);
|
|
2086
2459
|
};
|
|
2460
|
+
const injectNativeBridgeToNativeWebView = () => {
|
|
2461
|
+
if (!nativeWebViewRef.current) return;
|
|
2462
|
+
nativeWebViewRef.current.injectJavaScript(getInjectedWebViewScript());
|
|
2463
|
+
};
|
|
2087
2464
|
const handleNativeWebViewLoadEnd = () => {
|
|
2465
|
+
debugProvider("native WebView load end");
|
|
2466
|
+
injectNativeBridgeToNativeWebView();
|
|
2088
2467
|
postIdentityToNativeWebView();
|
|
2089
2468
|
widgetReadyRef.current = false;
|
|
2090
2469
|
startDelivery();
|
|
@@ -2098,11 +2477,11 @@ function SagepilotChatProvider({
|
|
|
2098
2477
|
if (widgetOrigin && readUrlOrigin(url) === widgetOrigin) {
|
|
2099
2478
|
return true;
|
|
2100
2479
|
}
|
|
2101
|
-
|
|
2480
|
+
Linking2.openURL(url).catch(() => void 0);
|
|
2102
2481
|
return false;
|
|
2103
2482
|
}, [state.conversationUrl, state.preloadUrl]);
|
|
2104
2483
|
useEffect(() => {
|
|
2105
|
-
if (
|
|
2484
|
+
if (Platform3.OS !== "web" || typeof window === "undefined") return;
|
|
2106
2485
|
const trustedWidgetOrigin = readUrlOrigin(state.conversationUrl);
|
|
2107
2486
|
if (!trustedWidgetOrigin) return;
|
|
2108
2487
|
const handleWindowMessage = (event) => {
|
|
@@ -2113,14 +2492,14 @@ function SagepilotChatProvider({
|
|
|
2113
2492
|
return () => window.removeEventListener("message", handleWindowMessage);
|
|
2114
2493
|
}, [state.conversationUrl]);
|
|
2115
2494
|
useEffect(() => {
|
|
2116
|
-
if (
|
|
2495
|
+
if (Platform3.OS !== "web" || !state.isPresented) return;
|
|
2117
2496
|
postIdentityToWebFrame();
|
|
2118
2497
|
}, [state.isPresented, state.conversationUrl, state.configured]);
|
|
2119
2498
|
useEffect(() => {
|
|
2120
|
-
if (
|
|
2499
|
+
if (Platform3.OS === "web" || !state.isPresented) return;
|
|
2121
2500
|
postIdentityToNativeWebView();
|
|
2122
2501
|
}, [state.isPresented, state.conversationUrl, state.configured]);
|
|
2123
|
-
if (
|
|
2502
|
+
if (Platform3.OS === "web") {
|
|
2124
2503
|
return createElement(
|
|
2125
2504
|
View,
|
|
2126
2505
|
{ style: styles.root },
|
|
@@ -2182,8 +2561,8 @@ function SagepilotChatProvider({
|
|
|
2182
2561
|
visible: state.configured && state.isPresented,
|
|
2183
2562
|
animationType,
|
|
2184
2563
|
presentationStyle: isFullScreenModal ? "fullScreen" : "pageSheet",
|
|
2185
|
-
statusBarTranslucent:
|
|
2186
|
-
navigationBarTranslucent:
|
|
2564
|
+
statusBarTranslucent: Platform3.OS === "android",
|
|
2565
|
+
navigationBarTranslucent: Platform3.OS === "android",
|
|
2187
2566
|
onRequestClose: () => internalSagepilotChat.dismiss()
|
|
2188
2567
|
},
|
|
2189
2568
|
createElement(
|
|
@@ -2214,7 +2593,7 @@ function SagepilotChatProvider({
|
|
|
2214
2593
|
// The imperceptible opacity toggle forces the Android WebView
|
|
2215
2594
|
// surface to re-composite on resume, clearing the blank-but-alive
|
|
2216
2595
|
// surface bug without a reload (see runLivenessProbe).
|
|
2217
|
-
style:
|
|
2596
|
+
style: Platform3.OS === "android" ? [styles.webview, { opacity: 1 - androidRepaintTick % 2 * 1e-3 }] : styles.webview,
|
|
2218
2597
|
startInLoadingState: true,
|
|
2219
2598
|
injectedJavaScriptBeforeContentLoaded: getInjectedWebViewScript(),
|
|
2220
2599
|
onMessage: handleWebViewMessage,
|
|
@@ -2252,6 +2631,7 @@ function SagepilotChatProvider({
|
|
|
2252
2631
|
createElement(
|
|
2253
2632
|
View,
|
|
2254
2633
|
{ style: styles.sheetCard },
|
|
2634
|
+
createElement(View, { style: styles.sheetHandle }),
|
|
2255
2635
|
createElement(Text, { style: styles.sheetTitle }, "Add attachment"),
|
|
2256
2636
|
...(internalSagepilotChat.getConfig()?.filePicker?.sources ?? []).map(
|
|
2257
2637
|
(source) => createElement(
|
|
@@ -2259,19 +2639,29 @@ function SagepilotChatProvider({
|
|
|
2259
2639
|
{
|
|
2260
2640
|
key: source,
|
|
2261
2641
|
accessibilityRole: "button",
|
|
2262
|
-
|
|
2642
|
+
android_ripple: { color: "rgba(15, 23, 42, 0.06)" },
|
|
2643
|
+
hitSlop: 4,
|
|
2644
|
+
style: ({ pressed }) => getSheetOptionStyle(pressed),
|
|
2263
2645
|
onPress: () => handleSourceChoice(source)
|
|
2264
2646
|
},
|
|
2265
|
-
|
|
2647
|
+
renderFilePickerSourceIcon(source),
|
|
2648
|
+
createElement(
|
|
2649
|
+
View,
|
|
2650
|
+
{ style: styles.sheetOptionText },
|
|
2651
|
+
createElement(Text, { style: styles.sheetButtonText }, FILE_PICKER_SOURCE_LABELS[source]),
|
|
2652
|
+
createElement(Text, { style: styles.sheetButtonDescription }, FILE_PICKER_SOURCE_DESCRIPTIONS[source])
|
|
2653
|
+
)
|
|
2266
2654
|
)
|
|
2267
2655
|
),
|
|
2268
2656
|
createElement(
|
|
2269
2657
|
Pressable,
|
|
2270
2658
|
{
|
|
2271
2659
|
accessibilityRole: "button",
|
|
2272
|
-
|
|
2660
|
+
android_ripple: { color: "rgba(15, 23, 42, 0.06)" },
|
|
2661
|
+
style: ({ pressed }) => getSheetCancelStyle(pressed),
|
|
2273
2662
|
onPress: () => setSourceChooser(null)
|
|
2274
2663
|
},
|
|
2664
|
+
createElement(X, { color: "#334155", size: 18, strokeWidth: 2.5, style: styles.sheetCancelIcon }),
|
|
2275
2665
|
createElement(Text, { style: styles.sheetCancelText }, "Cancel")
|
|
2276
2666
|
)
|
|
2277
2667
|
)
|
|
@@ -2377,43 +2767,109 @@ var styles = StyleSheet.create({
|
|
|
2377
2767
|
},
|
|
2378
2768
|
sheetBackdrop: {
|
|
2379
2769
|
flex: 1,
|
|
2770
|
+
alignItems: "center",
|
|
2380
2771
|
justifyContent: "flex-end",
|
|
2381
|
-
backgroundColor: "rgba(
|
|
2772
|
+
backgroundColor: "rgba(15, 23, 42, 0.48)"
|
|
2382
2773
|
},
|
|
2383
2774
|
sheetCard: {
|
|
2775
|
+
width: "100%",
|
|
2776
|
+
maxWidth: 540,
|
|
2384
2777
|
backgroundColor: "#ffffff",
|
|
2385
|
-
borderTopLeftRadius:
|
|
2386
|
-
borderTopRightRadius:
|
|
2387
|
-
paddingTop:
|
|
2388
|
-
paddingBottom:
|
|
2389
|
-
paddingHorizontal:
|
|
2778
|
+
borderTopLeftRadius: 26,
|
|
2779
|
+
borderTopRightRadius: 26,
|
|
2780
|
+
paddingTop: 10,
|
|
2781
|
+
paddingBottom: 18,
|
|
2782
|
+
paddingHorizontal: 14,
|
|
2783
|
+
shadowColor: "#0f172a",
|
|
2784
|
+
shadowOpacity: 0.22,
|
|
2785
|
+
shadowRadius: 28,
|
|
2786
|
+
shadowOffset: { width: 0, height: -10 },
|
|
2787
|
+
elevation: 18
|
|
2788
|
+
},
|
|
2789
|
+
sheetHandle: {
|
|
2790
|
+
alignSelf: "center",
|
|
2791
|
+
width: 42,
|
|
2792
|
+
height: 5,
|
|
2793
|
+
borderRadius: 999,
|
|
2794
|
+
backgroundColor: "#d1d5db",
|
|
2795
|
+
marginBottom: 14
|
|
2390
2796
|
},
|
|
2391
2797
|
sheetTitle: {
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2798
|
+
color: "#111827",
|
|
2799
|
+
fontSize: 17,
|
|
2800
|
+
fontWeight: "700",
|
|
2801
|
+
paddingBottom: 12,
|
|
2802
|
+
paddingHorizontal: 4
|
|
2397
2803
|
},
|
|
2398
|
-
|
|
2399
|
-
minHeight:
|
|
2804
|
+
sheetOption: {
|
|
2805
|
+
minHeight: 70,
|
|
2806
|
+
flexDirection: "row",
|
|
2807
|
+
alignItems: "center",
|
|
2808
|
+
borderRadius: 18,
|
|
2809
|
+
paddingHorizontal: 12,
|
|
2810
|
+
marginBottom: 6,
|
|
2811
|
+
backgroundColor: "#ffffff"
|
|
2812
|
+
},
|
|
2813
|
+
sheetOptionPressed: {
|
|
2814
|
+
backgroundColor: "#f8fafc",
|
|
2815
|
+
transform: [{ scale: 0.985 }]
|
|
2816
|
+
},
|
|
2817
|
+
sheetOptionIcon: {
|
|
2818
|
+
width: 46,
|
|
2819
|
+
height: 46,
|
|
2820
|
+
borderRadius: 16,
|
|
2821
|
+
marginRight: 14,
|
|
2400
2822
|
alignItems: "center",
|
|
2401
2823
|
justifyContent: "center",
|
|
2402
|
-
|
|
2824
|
+
position: "relative",
|
|
2825
|
+
overflow: "hidden"
|
|
2826
|
+
},
|
|
2827
|
+
sheetOptionText: {
|
|
2828
|
+
flex: 1,
|
|
2829
|
+
justifyContent: "center"
|
|
2403
2830
|
},
|
|
2404
2831
|
sheetButtonText: {
|
|
2405
|
-
color: "#
|
|
2832
|
+
color: "#0f172a",
|
|
2406
2833
|
fontSize: 16,
|
|
2407
|
-
fontWeight: "
|
|
2834
|
+
fontWeight: "700"
|
|
2835
|
+
},
|
|
2836
|
+
sheetButtonDescription: {
|
|
2837
|
+
color: "#64748b",
|
|
2838
|
+
fontSize: 13,
|
|
2839
|
+
fontWeight: "500",
|
|
2840
|
+
marginTop: 3
|
|
2841
|
+
},
|
|
2842
|
+
cameraOptionIcon: {
|
|
2843
|
+
backgroundColor: "#e0f2fe"
|
|
2844
|
+
},
|
|
2845
|
+
libraryOptionIcon: {
|
|
2846
|
+
backgroundColor: "#ecfdf5"
|
|
2847
|
+
},
|
|
2848
|
+
documentsOptionIcon: {
|
|
2849
|
+
backgroundColor: "#f5f3ff"
|
|
2408
2850
|
},
|
|
2409
2851
|
sheetCancelButton: {
|
|
2410
|
-
|
|
2411
|
-
|
|
2852
|
+
minHeight: 56,
|
|
2853
|
+
flexDirection: "row",
|
|
2854
|
+
alignItems: "center",
|
|
2855
|
+
justifyContent: "center",
|
|
2856
|
+
borderRadius: 18,
|
|
2857
|
+
marginTop: 8,
|
|
2858
|
+
backgroundColor: "#f1f5f9"
|
|
2859
|
+
},
|
|
2860
|
+
sheetCancelButtonPressed: {
|
|
2861
|
+
backgroundColor: "#e2e8f0",
|
|
2862
|
+
transform: [{ scale: 0.985 }]
|
|
2863
|
+
},
|
|
2864
|
+
sheetCancelIcon: {
|
|
2865
|
+
width: 18,
|
|
2866
|
+
height: 18,
|
|
2867
|
+
marginRight: 8
|
|
2412
2868
|
},
|
|
2413
2869
|
sheetCancelText: {
|
|
2414
|
-
color: "#
|
|
2870
|
+
color: "#0f172a",
|
|
2415
2871
|
fontSize: 16,
|
|
2416
|
-
fontWeight: "
|
|
2872
|
+
fontWeight: "700"
|
|
2417
2873
|
}
|
|
2418
2874
|
});
|
|
2419
2875
|
|
|
@@ -2467,6 +2923,9 @@ function useSagepilotChat() {
|
|
|
2467
2923
|
};
|
|
2468
2924
|
}
|
|
2469
2925
|
export {
|
|
2926
|
+
SAGEPILOT_DEFAULT_IMAGE_MAX_DIMENSION,
|
|
2927
|
+
SAGEPILOT_DEFAULT_IMAGE_MAX_FILE_SIZE_BYTES,
|
|
2928
|
+
SAGEPILOT_DEFAULT_IMAGE_QUALITY,
|
|
2470
2929
|
SagepilotChat,
|
|
2471
2930
|
SagepilotChatError,
|
|
2472
2931
|
SagepilotChatProvider,
|
|
@@ -2475,5 +2934,7 @@ export {
|
|
|
2475
2934
|
createKeychainTokenStorage,
|
|
2476
2935
|
createSagepilotFilePicker,
|
|
2477
2936
|
createSagepilotFileStore,
|
|
2937
|
+
ensureSagepilotAndroidCameraPermission,
|
|
2938
|
+
promptOpenSagepilotCameraSettings,
|
|
2478
2939
|
useSagepilotChat
|
|
2479
2940
|
};
|