@reverbia/sdk 1.0.0-next.20251217134403 → 1.0.0-next.20251217144159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/index.cjs +869 -0
- package/dist/react/index.d.mts +350 -1
- package/dist/react/index.d.ts +350 -1
- package/dist/react/index.mjs +866 -0
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -4225,14 +4225,875 @@ var extractConversationContext = (messages, maxMessages = 3) => {
|
|
|
4225
4225
|
const userMessages = messages.filter((msg) => msg.role === "user").slice(-maxMessages).map((msg) => msg.content).join(" ");
|
|
4226
4226
|
return userMessages.trim();
|
|
4227
4227
|
};
|
|
4228
|
+
|
|
4229
|
+
// src/react/useDropboxBackup.ts
|
|
4230
|
+
import { useCallback as useCallback11, useMemo as useMemo4 } from "react";
|
|
4231
|
+
|
|
4232
|
+
// src/lib/backup/dropbox/api.ts
|
|
4233
|
+
var DROPBOX_API_URL = "https://api.dropboxapi.com/2";
|
|
4234
|
+
var DROPBOX_CONTENT_URL = "https://content.dropboxapi.com/2";
|
|
4235
|
+
var DEFAULT_BACKUP_FOLDER = "/ai-chat-app/conversations";
|
|
4236
|
+
async function ensureBackupFolder(accessToken, folder = DEFAULT_BACKUP_FOLDER) {
|
|
4237
|
+
try {
|
|
4238
|
+
await fetch(`${DROPBOX_API_URL}/files/create_folder_v2`, {
|
|
4239
|
+
method: "POST",
|
|
4240
|
+
headers: {
|
|
4241
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4242
|
+
"Content-Type": "application/json"
|
|
4243
|
+
},
|
|
4244
|
+
body: JSON.stringify({
|
|
4245
|
+
path: folder,
|
|
4246
|
+
autorename: false
|
|
4247
|
+
})
|
|
4248
|
+
});
|
|
4249
|
+
} catch {
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
async function uploadFileToDropbox(accessToken, filename, content, folder = DEFAULT_BACKUP_FOLDER) {
|
|
4253
|
+
await ensureBackupFolder(accessToken, folder);
|
|
4254
|
+
const path = `${folder}/${filename}`;
|
|
4255
|
+
const response = await fetch(`${DROPBOX_CONTENT_URL}/files/upload`, {
|
|
4256
|
+
method: "POST",
|
|
4257
|
+
headers: {
|
|
4258
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4259
|
+
"Content-Type": "application/octet-stream",
|
|
4260
|
+
"Dropbox-API-Arg": JSON.stringify({
|
|
4261
|
+
path,
|
|
4262
|
+
mode: "overwrite",
|
|
4263
|
+
autorename: false,
|
|
4264
|
+
mute: true
|
|
4265
|
+
})
|
|
4266
|
+
},
|
|
4267
|
+
body: content
|
|
4268
|
+
});
|
|
4269
|
+
if (!response.ok) {
|
|
4270
|
+
const errorText = await response.text();
|
|
4271
|
+
throw new Error(`Dropbox upload failed: ${response.status} - ${errorText}`);
|
|
4272
|
+
}
|
|
4273
|
+
return response.json();
|
|
4274
|
+
}
|
|
4275
|
+
async function listDropboxFiles(accessToken, folder = DEFAULT_BACKUP_FOLDER) {
|
|
4276
|
+
await ensureBackupFolder(accessToken, folder);
|
|
4277
|
+
const response = await fetch(`${DROPBOX_API_URL}/files/list_folder`, {
|
|
4278
|
+
method: "POST",
|
|
4279
|
+
headers: {
|
|
4280
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4281
|
+
"Content-Type": "application/json"
|
|
4282
|
+
},
|
|
4283
|
+
body: JSON.stringify({
|
|
4284
|
+
path: folder,
|
|
4285
|
+
recursive: false,
|
|
4286
|
+
include_deleted: false
|
|
4287
|
+
})
|
|
4288
|
+
});
|
|
4289
|
+
if (!response.ok) {
|
|
4290
|
+
const error = await response.json();
|
|
4291
|
+
if (error.error?.path?.[".tag"] === "not_found") {
|
|
4292
|
+
return [];
|
|
4293
|
+
}
|
|
4294
|
+
throw new Error(`Dropbox list failed: ${error.error_summary}`);
|
|
4295
|
+
}
|
|
4296
|
+
let data = await response.json();
|
|
4297
|
+
const allEntries = [...data.entries];
|
|
4298
|
+
while (data.has_more) {
|
|
4299
|
+
const continueResponse = await fetch(`${DROPBOX_API_URL}/files/list_folder/continue`, {
|
|
4300
|
+
method: "POST",
|
|
4301
|
+
headers: {
|
|
4302
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4303
|
+
"Content-Type": "application/json"
|
|
4304
|
+
},
|
|
4305
|
+
body: JSON.stringify({
|
|
4306
|
+
cursor: data.cursor
|
|
4307
|
+
})
|
|
4308
|
+
});
|
|
4309
|
+
if (!continueResponse.ok) {
|
|
4310
|
+
const errorText = await continueResponse.text();
|
|
4311
|
+
throw new Error(`Dropbox list continue failed: ${continueResponse.status} - ${errorText}`);
|
|
4312
|
+
}
|
|
4313
|
+
data = await continueResponse.json();
|
|
4314
|
+
allEntries.push(...data.entries);
|
|
4315
|
+
}
|
|
4316
|
+
const files = allEntries.filter((entry) => entry[".tag"] === "file").map((entry) => ({
|
|
4317
|
+
id: entry.id,
|
|
4318
|
+
name: entry.name,
|
|
4319
|
+
path_lower: entry.path_lower,
|
|
4320
|
+
path_display: entry.path_display,
|
|
4321
|
+
client_modified: entry.client_modified || "",
|
|
4322
|
+
server_modified: entry.server_modified || "",
|
|
4323
|
+
size: entry.size || 0
|
|
4324
|
+
}));
|
|
4325
|
+
return files;
|
|
4326
|
+
}
|
|
4327
|
+
async function downloadDropboxFile(accessToken, path) {
|
|
4328
|
+
const response = await fetch(`${DROPBOX_CONTENT_URL}/files/download`, {
|
|
4329
|
+
method: "POST",
|
|
4330
|
+
headers: {
|
|
4331
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4332
|
+
"Dropbox-API-Arg": JSON.stringify({ path })
|
|
4333
|
+
}
|
|
4334
|
+
});
|
|
4335
|
+
if (!response.ok) {
|
|
4336
|
+
throw new Error(`Dropbox download failed: ${response.status}`);
|
|
4337
|
+
}
|
|
4338
|
+
return response.blob();
|
|
4339
|
+
}
|
|
4340
|
+
async function findDropboxFile(accessToken, filename, folder = DEFAULT_BACKUP_FOLDER) {
|
|
4341
|
+
const files = await listDropboxFiles(accessToken, folder);
|
|
4342
|
+
return files.find((f) => f.name === filename) || null;
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
// src/lib/backup/dropbox/backup.ts
|
|
4346
|
+
var isAuthError = (err) => err instanceof Error && (err.message.includes("401") || err.message.includes("invalid_access_token"));
|
|
4347
|
+
async function pushConversationToDropbox(database, conversationId, userAddress, token, deps, backupFolder = DEFAULT_BACKUP_FOLDER, _retried = false) {
|
|
4348
|
+
try {
|
|
4349
|
+
await deps.requestEncryptionKey(userAddress);
|
|
4350
|
+
const filename = `${conversationId}.json`;
|
|
4351
|
+
const existingFile = await findDropboxFile(token, filename, backupFolder);
|
|
4352
|
+
if (existingFile) {
|
|
4353
|
+
const { Q: Q4 } = await import("@nozbe/watermelondb");
|
|
4354
|
+
const conversationsCollection = database.get("conversations");
|
|
4355
|
+
const records = await conversationsCollection.query(Q4.where("conversation_id", conversationId)).fetch();
|
|
4356
|
+
if (records.length > 0) {
|
|
4357
|
+
const conversation = conversationToStored(records[0]);
|
|
4358
|
+
const localUpdated = conversation.updatedAt.getTime();
|
|
4359
|
+
const remoteModified = new Date(existingFile.server_modified).getTime();
|
|
4360
|
+
if (localUpdated <= remoteModified) {
|
|
4361
|
+
return "skipped";
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
const exportResult = await deps.exportConversation(conversationId, userAddress);
|
|
4366
|
+
if (!exportResult.success || !exportResult.blob) {
|
|
4367
|
+
return "failed";
|
|
4368
|
+
}
|
|
4369
|
+
await uploadFileToDropbox(token, filename, exportResult.blob, backupFolder);
|
|
4370
|
+
return "uploaded";
|
|
4371
|
+
} catch (err) {
|
|
4372
|
+
if (isAuthError(err) && !_retried) {
|
|
4373
|
+
try {
|
|
4374
|
+
const newToken = await deps.requestDropboxAccess();
|
|
4375
|
+
return pushConversationToDropbox(database, conversationId, userAddress, newToken, deps, backupFolder, true);
|
|
4376
|
+
} catch {
|
|
4377
|
+
return "failed";
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
return "failed";
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
async function performDropboxExport(database, userAddress, token, deps, onProgress, backupFolder = DEFAULT_BACKUP_FOLDER) {
|
|
4384
|
+
await deps.requestEncryptionKey(userAddress);
|
|
4385
|
+
const { Q: Q4 } = await import("@nozbe/watermelondb");
|
|
4386
|
+
const conversationsCollection = database.get("conversations");
|
|
4387
|
+
const records = await conversationsCollection.query(Q4.where("is_deleted", false)).fetch();
|
|
4388
|
+
const conversations = records.map(conversationToStored);
|
|
4389
|
+
const total = conversations.length;
|
|
4390
|
+
if (total === 0) {
|
|
4391
|
+
return { success: true, uploaded: 0, skipped: 0, total: 0 };
|
|
4392
|
+
}
|
|
4393
|
+
let uploaded = 0;
|
|
4394
|
+
let skipped = 0;
|
|
4395
|
+
for (let i = 0; i < conversations.length; i++) {
|
|
4396
|
+
const conv = conversations[i];
|
|
4397
|
+
onProgress?.(i + 1, total);
|
|
4398
|
+
const result = await pushConversationToDropbox(
|
|
4399
|
+
database,
|
|
4400
|
+
conv.conversationId,
|
|
4401
|
+
userAddress,
|
|
4402
|
+
token,
|
|
4403
|
+
deps,
|
|
4404
|
+
backupFolder
|
|
4405
|
+
);
|
|
4406
|
+
if (result === "uploaded") uploaded++;
|
|
4407
|
+
if (result === "skipped") skipped++;
|
|
4408
|
+
}
|
|
4409
|
+
return { success: true, uploaded, skipped, total };
|
|
4410
|
+
}
|
|
4411
|
+
async function performDropboxImport(userAddress, token, deps, onProgress, backupFolder = DEFAULT_BACKUP_FOLDER) {
|
|
4412
|
+
await deps.requestEncryptionKey(userAddress);
|
|
4413
|
+
const remoteFiles = await listDropboxFiles(token, backupFolder);
|
|
4414
|
+
if (remoteFiles.length === 0) {
|
|
4415
|
+
return { success: false, restored: 0, failed: 0, total: 0, noBackupsFound: true };
|
|
4416
|
+
}
|
|
4417
|
+
const jsonFiles = remoteFiles.filter((file) => file.name.endsWith(".json"));
|
|
4418
|
+
const total = jsonFiles.length;
|
|
4419
|
+
let restored = 0;
|
|
4420
|
+
let failed = 0;
|
|
4421
|
+
let currentToken = token;
|
|
4422
|
+
for (let i = 0; i < jsonFiles.length; i++) {
|
|
4423
|
+
const file = jsonFiles[i];
|
|
4424
|
+
onProgress?.(i + 1, total);
|
|
4425
|
+
try {
|
|
4426
|
+
const blob = await downloadDropboxFile(currentToken, file.path_lower);
|
|
4427
|
+
const result = await deps.importConversation(blob, userAddress);
|
|
4428
|
+
if (result.success) {
|
|
4429
|
+
restored++;
|
|
4430
|
+
} else {
|
|
4431
|
+
failed++;
|
|
4432
|
+
}
|
|
4433
|
+
} catch (err) {
|
|
4434
|
+
if (isAuthError(err)) {
|
|
4435
|
+
try {
|
|
4436
|
+
currentToken = await deps.requestDropboxAccess();
|
|
4437
|
+
const blob = await downloadDropboxFile(currentToken, file.path_lower);
|
|
4438
|
+
const result = await deps.importConversation(blob, userAddress);
|
|
4439
|
+
if (result.success) {
|
|
4440
|
+
restored++;
|
|
4441
|
+
} else {
|
|
4442
|
+
failed++;
|
|
4443
|
+
}
|
|
4444
|
+
} catch {
|
|
4445
|
+
failed++;
|
|
4446
|
+
}
|
|
4447
|
+
} else {
|
|
4448
|
+
failed++;
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
return { success: true, restored, failed, total };
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4455
|
+
// src/react/useDropboxAuth.ts
|
|
4456
|
+
import {
|
|
4457
|
+
createContext,
|
|
4458
|
+
createElement,
|
|
4459
|
+
useCallback as useCallback10,
|
|
4460
|
+
useContext,
|
|
4461
|
+
useEffect as useEffect6,
|
|
4462
|
+
useState as useState10
|
|
4463
|
+
} from "react";
|
|
4464
|
+
|
|
4465
|
+
// src/lib/backup/dropbox/auth.ts
|
|
4466
|
+
var DROPBOX_AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
|
|
4467
|
+
var DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token";
|
|
4468
|
+
var TOKEN_STORAGE_KEY = "dropbox_access_token";
|
|
4469
|
+
var VERIFIER_STORAGE_KEY = "dropbox_code_verifier";
|
|
4470
|
+
function generateCodeVerifier() {
|
|
4471
|
+
const array = new Uint8Array(32);
|
|
4472
|
+
crypto.getRandomValues(array);
|
|
4473
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
4474
|
+
}
|
|
4475
|
+
async function generateCodeChallenge(verifier) {
|
|
4476
|
+
const encoder = new TextEncoder();
|
|
4477
|
+
const data = encoder.encode(verifier);
|
|
4478
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
4479
|
+
const base64 = btoa(String.fromCharCode(...new Uint8Array(hash)));
|
|
4480
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
4481
|
+
}
|
|
4482
|
+
function getStoredToken() {
|
|
4483
|
+
if (typeof window === "undefined") return null;
|
|
4484
|
+
return sessionStorage.getItem(TOKEN_STORAGE_KEY);
|
|
4485
|
+
}
|
|
4486
|
+
function storeToken(token) {
|
|
4487
|
+
if (typeof window === "undefined") return;
|
|
4488
|
+
sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
|
|
4489
|
+
}
|
|
4490
|
+
function clearToken() {
|
|
4491
|
+
if (typeof window === "undefined") return;
|
|
4492
|
+
sessionStorage.removeItem(TOKEN_STORAGE_KEY);
|
|
4493
|
+
}
|
|
4494
|
+
function getStoredVerifier() {
|
|
4495
|
+
if (typeof window === "undefined") return null;
|
|
4496
|
+
return sessionStorage.getItem(VERIFIER_STORAGE_KEY);
|
|
4497
|
+
}
|
|
4498
|
+
function storeVerifier(verifier) {
|
|
4499
|
+
if (typeof window === "undefined") return;
|
|
4500
|
+
sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
|
|
4501
|
+
}
|
|
4502
|
+
function clearVerifier() {
|
|
4503
|
+
if (typeof window === "undefined") return;
|
|
4504
|
+
sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
|
|
4505
|
+
}
|
|
4506
|
+
function getRedirectUri(callbackPath) {
|
|
4507
|
+
if (typeof window === "undefined") return "";
|
|
4508
|
+
return `${window.location.origin}${callbackPath}`;
|
|
4509
|
+
}
|
|
4510
|
+
async function handleDropboxCallback(appKey, callbackPath) {
|
|
4511
|
+
if (typeof window === "undefined") return null;
|
|
4512
|
+
const url = new URL(window.location.href);
|
|
4513
|
+
const code = url.searchParams.get("code");
|
|
4514
|
+
const state = url.searchParams.get("state");
|
|
4515
|
+
if (!code || state !== "dropbox_auth") return null;
|
|
4516
|
+
const verifier = getStoredVerifier();
|
|
4517
|
+
if (!verifier) return null;
|
|
4518
|
+
try {
|
|
4519
|
+
const response = await fetch(DROPBOX_TOKEN_URL, {
|
|
4520
|
+
method: "POST",
|
|
4521
|
+
headers: {
|
|
4522
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
4523
|
+
},
|
|
4524
|
+
body: new URLSearchParams({
|
|
4525
|
+
code,
|
|
4526
|
+
grant_type: "authorization_code",
|
|
4527
|
+
client_id: appKey,
|
|
4528
|
+
redirect_uri: getRedirectUri(callbackPath),
|
|
4529
|
+
code_verifier: verifier
|
|
4530
|
+
})
|
|
4531
|
+
});
|
|
4532
|
+
if (!response.ok) {
|
|
4533
|
+
throw new Error("Token exchange failed");
|
|
4534
|
+
}
|
|
4535
|
+
const data = await response.json();
|
|
4536
|
+
const token = data.access_token;
|
|
4537
|
+
if (typeof token !== "string" || token.trim() === "") {
|
|
4538
|
+
throw new Error("Invalid token response: access_token is missing or empty");
|
|
4539
|
+
}
|
|
4540
|
+
storeToken(token);
|
|
4541
|
+
clearVerifier();
|
|
4542
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
4543
|
+
return token;
|
|
4544
|
+
} catch {
|
|
4545
|
+
clearVerifier();
|
|
4546
|
+
return null;
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
async function startDropboxAuth(appKey, callbackPath) {
|
|
4550
|
+
const verifier = generateCodeVerifier();
|
|
4551
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
4552
|
+
storeVerifier(verifier);
|
|
4553
|
+
const params = new URLSearchParams({
|
|
4554
|
+
client_id: appKey,
|
|
4555
|
+
redirect_uri: getRedirectUri(callbackPath),
|
|
4556
|
+
response_type: "code",
|
|
4557
|
+
code_challenge: challenge,
|
|
4558
|
+
code_challenge_method: "S256",
|
|
4559
|
+
state: "dropbox_auth",
|
|
4560
|
+
token_access_type: "offline"
|
|
4561
|
+
});
|
|
4562
|
+
window.location.href = `${DROPBOX_AUTH_URL}?${params.toString()}`;
|
|
4563
|
+
return new Promise(() => {
|
|
4564
|
+
});
|
|
4565
|
+
}
|
|
4566
|
+
async function requestDropboxAccess(appKey, callbackPath) {
|
|
4567
|
+
if (!appKey) {
|
|
4568
|
+
throw new Error("Dropbox is not configured");
|
|
4569
|
+
}
|
|
4570
|
+
const storedToken = getStoredToken();
|
|
4571
|
+
if (storedToken) {
|
|
4572
|
+
return storedToken;
|
|
4573
|
+
}
|
|
4574
|
+
return startDropboxAuth(appKey, callbackPath);
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
// src/react/useDropboxAuth.ts
|
|
4578
|
+
var DropboxAuthContext = createContext(null);
|
|
4579
|
+
function DropboxAuthProvider({
|
|
4580
|
+
appKey,
|
|
4581
|
+
callbackPath = "/auth/dropbox/callback",
|
|
4582
|
+
children
|
|
4583
|
+
}) {
|
|
4584
|
+
const [accessToken, setAccessToken] = useState10(null);
|
|
4585
|
+
const isConfigured = !!appKey;
|
|
4586
|
+
useEffect6(() => {
|
|
4587
|
+
const storedToken = getStoredToken();
|
|
4588
|
+
if (storedToken) {
|
|
4589
|
+
setAccessToken(storedToken);
|
|
4590
|
+
}
|
|
4591
|
+
}, []);
|
|
4592
|
+
useEffect6(() => {
|
|
4593
|
+
if (!isConfigured || !appKey) return;
|
|
4594
|
+
const handleCallback = async () => {
|
|
4595
|
+
const token = await handleDropboxCallback(appKey, callbackPath);
|
|
4596
|
+
if (token) {
|
|
4597
|
+
setAccessToken(token);
|
|
4598
|
+
}
|
|
4599
|
+
};
|
|
4600
|
+
handleCallback();
|
|
4601
|
+
}, [appKey, callbackPath, isConfigured]);
|
|
4602
|
+
const requestAccess = useCallback10(async () => {
|
|
4603
|
+
if (!isConfigured || !appKey) {
|
|
4604
|
+
throw new Error("Dropbox is not configured");
|
|
4605
|
+
}
|
|
4606
|
+
if (accessToken) {
|
|
4607
|
+
return accessToken;
|
|
4608
|
+
}
|
|
4609
|
+
const storedToken = getStoredToken();
|
|
4610
|
+
if (storedToken) {
|
|
4611
|
+
setAccessToken(storedToken);
|
|
4612
|
+
return storedToken;
|
|
4613
|
+
}
|
|
4614
|
+
return requestDropboxAccess(appKey, callbackPath);
|
|
4615
|
+
}, [accessToken, appKey, callbackPath, isConfigured]);
|
|
4616
|
+
const logout = useCallback10(() => {
|
|
4617
|
+
clearToken();
|
|
4618
|
+
setAccessToken(null);
|
|
4619
|
+
}, []);
|
|
4620
|
+
return createElement(
|
|
4621
|
+
DropboxAuthContext.Provider,
|
|
4622
|
+
{
|
|
4623
|
+
value: {
|
|
4624
|
+
accessToken,
|
|
4625
|
+
isAuthenticated: !!accessToken,
|
|
4626
|
+
isConfigured,
|
|
4627
|
+
requestAccess,
|
|
4628
|
+
logout
|
|
4629
|
+
}
|
|
4630
|
+
},
|
|
4631
|
+
children
|
|
4632
|
+
);
|
|
4633
|
+
}
|
|
4634
|
+
function useDropboxAuth() {
|
|
4635
|
+
const context = useContext(DropboxAuthContext);
|
|
4636
|
+
if (!context) {
|
|
4637
|
+
throw new Error("useDropboxAuth must be used within DropboxAuthProvider");
|
|
4638
|
+
}
|
|
4639
|
+
return context;
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
// src/react/useDropboxBackup.ts
|
|
4643
|
+
function useDropboxBackup(options) {
|
|
4644
|
+
const {
|
|
4645
|
+
database,
|
|
4646
|
+
userAddress,
|
|
4647
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
4648
|
+
exportConversation,
|
|
4649
|
+
importConversation,
|
|
4650
|
+
backupFolder = DEFAULT_BACKUP_FOLDER
|
|
4651
|
+
} = options;
|
|
4652
|
+
const {
|
|
4653
|
+
accessToken: dropboxToken,
|
|
4654
|
+
isConfigured: isDropboxConfigured,
|
|
4655
|
+
requestAccess: requestDropboxAccess2
|
|
4656
|
+
} = useDropboxAuth();
|
|
4657
|
+
const deps = useMemo4(
|
|
4658
|
+
() => ({
|
|
4659
|
+
requestDropboxAccess: requestDropboxAccess2,
|
|
4660
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
4661
|
+
exportConversation,
|
|
4662
|
+
importConversation
|
|
4663
|
+
}),
|
|
4664
|
+
[requestDropboxAccess2, requestEncryptionKey2, exportConversation, importConversation]
|
|
4665
|
+
);
|
|
4666
|
+
const ensureToken = useCallback11(async () => {
|
|
4667
|
+
if (dropboxToken) return dropboxToken;
|
|
4668
|
+
try {
|
|
4669
|
+
return await requestDropboxAccess2();
|
|
4670
|
+
} catch {
|
|
4671
|
+
return null;
|
|
4672
|
+
}
|
|
4673
|
+
}, [dropboxToken, requestDropboxAccess2]);
|
|
4674
|
+
const backup = useCallback11(
|
|
4675
|
+
async (backupOptions) => {
|
|
4676
|
+
if (!userAddress) {
|
|
4677
|
+
return { error: "Please sign in to backup to Dropbox" };
|
|
4678
|
+
}
|
|
4679
|
+
const token = await ensureToken();
|
|
4680
|
+
if (!token) {
|
|
4681
|
+
return { error: "Dropbox access denied" };
|
|
4682
|
+
}
|
|
4683
|
+
try {
|
|
4684
|
+
return await performDropboxExport(
|
|
4685
|
+
database,
|
|
4686
|
+
userAddress,
|
|
4687
|
+
token,
|
|
4688
|
+
deps,
|
|
4689
|
+
backupOptions?.onProgress,
|
|
4690
|
+
backupFolder
|
|
4691
|
+
);
|
|
4692
|
+
} catch (err) {
|
|
4693
|
+
return {
|
|
4694
|
+
error: err instanceof Error ? err.message : "Failed to backup to Dropbox"
|
|
4695
|
+
};
|
|
4696
|
+
}
|
|
4697
|
+
},
|
|
4698
|
+
[database, userAddress, ensureToken, deps, backupFolder]
|
|
4699
|
+
);
|
|
4700
|
+
const restore = useCallback11(
|
|
4701
|
+
async (restoreOptions) => {
|
|
4702
|
+
if (!userAddress) {
|
|
4703
|
+
return { error: "Please sign in to restore from Dropbox" };
|
|
4704
|
+
}
|
|
4705
|
+
const token = await ensureToken();
|
|
4706
|
+
if (!token) {
|
|
4707
|
+
return { error: "Dropbox access denied" };
|
|
4708
|
+
}
|
|
4709
|
+
try {
|
|
4710
|
+
return await performDropboxImport(
|
|
4711
|
+
userAddress,
|
|
4712
|
+
token,
|
|
4713
|
+
deps,
|
|
4714
|
+
restoreOptions?.onProgress,
|
|
4715
|
+
backupFolder
|
|
4716
|
+
);
|
|
4717
|
+
} catch (err) {
|
|
4718
|
+
return {
|
|
4719
|
+
error: err instanceof Error ? err.message : "Failed to restore from Dropbox"
|
|
4720
|
+
};
|
|
4721
|
+
}
|
|
4722
|
+
},
|
|
4723
|
+
[userAddress, ensureToken, deps, backupFolder]
|
|
4724
|
+
);
|
|
4725
|
+
return {
|
|
4726
|
+
backup,
|
|
4727
|
+
restore,
|
|
4728
|
+
isConfigured: isDropboxConfigured,
|
|
4729
|
+
isAuthenticated: !!dropboxToken
|
|
4730
|
+
};
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4733
|
+
// src/react/useGoogleDriveBackup.ts
|
|
4734
|
+
import { useCallback as useCallback12, useMemo as useMemo5 } from "react";
|
|
4735
|
+
|
|
4736
|
+
// src/lib/backup/google/api.ts
|
|
4737
|
+
var DRIVE_API_URL = "https://www.googleapis.com/drive/v3";
|
|
4738
|
+
var DRIVE_UPLOAD_URL = "https://www.googleapis.com/upload/drive/v3";
|
|
4739
|
+
var FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
|
|
4740
|
+
function escapeQueryValue(value) {
|
|
4741
|
+
return value.replace(/'/g, "''");
|
|
4742
|
+
}
|
|
4743
|
+
var DEFAULT_ROOT_FOLDER = "ai-chat-app";
|
|
4744
|
+
var DEFAULT_CONVERSATIONS_FOLDER = "conversations";
|
|
4745
|
+
async function ensureFolder(accessToken, name, parentId) {
|
|
4746
|
+
const parentQuery = parentId ? `'${escapeQueryValue(parentId)}' in parents and ` : "";
|
|
4747
|
+
const query = `${parentQuery}mimeType='${FOLDER_MIME_TYPE}' and name='${escapeQueryValue(name)}' and trashed=false`;
|
|
4748
|
+
const response = await fetch(
|
|
4749
|
+
`${DRIVE_API_URL}/files?q=${encodeURIComponent(query)}&fields=files(id)`,
|
|
4750
|
+
{
|
|
4751
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
4752
|
+
}
|
|
4753
|
+
);
|
|
4754
|
+
if (!response.ok) {
|
|
4755
|
+
throw new Error(`Failed to search for folder ${name}: ${response.status}`);
|
|
4756
|
+
}
|
|
4757
|
+
const data = await response.json();
|
|
4758
|
+
if (data.files && data.files.length > 0) {
|
|
4759
|
+
return data.files[0].id;
|
|
4760
|
+
}
|
|
4761
|
+
const body = {
|
|
4762
|
+
name,
|
|
4763
|
+
mimeType: FOLDER_MIME_TYPE
|
|
4764
|
+
};
|
|
4765
|
+
if (parentId) {
|
|
4766
|
+
body.parents = [parentId];
|
|
4767
|
+
}
|
|
4768
|
+
const createResponse = await fetch(`${DRIVE_API_URL}/files`, {
|
|
4769
|
+
method: "POST",
|
|
4770
|
+
headers: {
|
|
4771
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4772
|
+
"Content-Type": "application/json"
|
|
4773
|
+
},
|
|
4774
|
+
body: JSON.stringify(body)
|
|
4775
|
+
});
|
|
4776
|
+
if (!createResponse.ok) {
|
|
4777
|
+
throw new Error(`Failed to create folder ${name}: ${createResponse.status}`);
|
|
4778
|
+
}
|
|
4779
|
+
const folderData = await createResponse.json();
|
|
4780
|
+
return folderData.id;
|
|
4781
|
+
}
|
|
4782
|
+
async function getBackupFolder(accessToken, rootFolder = DEFAULT_ROOT_FOLDER, subfolder = DEFAULT_CONVERSATIONS_FOLDER) {
|
|
4783
|
+
const rootId = await ensureFolder(accessToken, rootFolder);
|
|
4784
|
+
return ensureFolder(accessToken, subfolder, rootId);
|
|
4785
|
+
}
|
|
4786
|
+
async function uploadFileToDrive(accessToken, folderId, content, filename) {
|
|
4787
|
+
const metadata = {
|
|
4788
|
+
name: filename,
|
|
4789
|
+
parents: [folderId],
|
|
4790
|
+
mimeType: "application/json"
|
|
4791
|
+
};
|
|
4792
|
+
const form = new FormData();
|
|
4793
|
+
form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }));
|
|
4794
|
+
form.append("file", content);
|
|
4795
|
+
const response = await fetch(`${DRIVE_UPLOAD_URL}/files?uploadType=multipart`, {
|
|
4796
|
+
method: "POST",
|
|
4797
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
4798
|
+
body: form
|
|
4799
|
+
});
|
|
4800
|
+
if (!response.ok) {
|
|
4801
|
+
const errorText = await response.text();
|
|
4802
|
+
throw new Error(`Drive upload failed: ${response.status} - ${errorText}`);
|
|
4803
|
+
}
|
|
4804
|
+
return response.json();
|
|
4805
|
+
}
|
|
4806
|
+
async function updateDriveFile(accessToken, fileId, content) {
|
|
4807
|
+
const response = await fetch(`${DRIVE_UPLOAD_URL}/files/${fileId}?uploadType=media`, {
|
|
4808
|
+
method: "PATCH",
|
|
4809
|
+
headers: {
|
|
4810
|
+
Authorization: `Bearer ${accessToken}`,
|
|
4811
|
+
"Content-Type": "application/json"
|
|
4812
|
+
},
|
|
4813
|
+
body: content
|
|
4814
|
+
});
|
|
4815
|
+
if (!response.ok) {
|
|
4816
|
+
const errorText = await response.text();
|
|
4817
|
+
throw new Error(`Drive update failed: ${response.status} - ${errorText}`);
|
|
4818
|
+
}
|
|
4819
|
+
return response.json();
|
|
4820
|
+
}
|
|
4821
|
+
async function listDriveFiles(accessToken, folderId) {
|
|
4822
|
+
const query = `'${escapeQueryValue(folderId)}' in parents and mimeType='application/json' and trashed=false`;
|
|
4823
|
+
const fields = "files(id,name,createdTime,modifiedTime,size)";
|
|
4824
|
+
const response = await fetch(
|
|
4825
|
+
`${DRIVE_API_URL}/files?q=${encodeURIComponent(query)}&fields=${fields}&pageSize=1000`,
|
|
4826
|
+
{
|
|
4827
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
4828
|
+
}
|
|
4829
|
+
);
|
|
4830
|
+
if (!response.ok) {
|
|
4831
|
+
throw new Error(`Failed to list files: ${response.status}`);
|
|
4832
|
+
}
|
|
4833
|
+
const data = await response.json();
|
|
4834
|
+
return data.files ?? [];
|
|
4835
|
+
}
|
|
4836
|
+
async function downloadDriveFile(accessToken, fileId) {
|
|
4837
|
+
const response = await fetch(`${DRIVE_API_URL}/files/${fileId}?alt=media`, {
|
|
4838
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
4839
|
+
});
|
|
4840
|
+
if (!response.ok) {
|
|
4841
|
+
throw new Error(`Failed to download file: ${response.status}`);
|
|
4842
|
+
}
|
|
4843
|
+
return response.blob();
|
|
4844
|
+
}
|
|
4845
|
+
async function findDriveFile(accessToken, folderId, filename) {
|
|
4846
|
+
const query = `'${escapeQueryValue(folderId)}' in parents and name='${escapeQueryValue(filename)}' and trashed=false`;
|
|
4847
|
+
const fields = "files(id,name,createdTime,modifiedTime,size)";
|
|
4848
|
+
const response = await fetch(
|
|
4849
|
+
`${DRIVE_API_URL}/files?q=${encodeURIComponent(query)}&fields=${fields}&pageSize=1`,
|
|
4850
|
+
{
|
|
4851
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
4852
|
+
}
|
|
4853
|
+
);
|
|
4854
|
+
if (!response.ok) {
|
|
4855
|
+
throw new Error(`Failed to find file: ${response.status}`);
|
|
4856
|
+
}
|
|
4857
|
+
const data = await response.json();
|
|
4858
|
+
return data.files?.[0] ?? null;
|
|
4859
|
+
}
|
|
4860
|
+
|
|
4861
|
+
// src/lib/backup/google/backup.ts
|
|
4862
|
+
var isAuthError2 = (err) => err instanceof Error && (err.message.includes("401") || err.message.includes("403"));
|
|
4863
|
+
async function getConversationsFolder(token, requestDriveAccess, rootFolder, subfolder) {
|
|
4864
|
+
try {
|
|
4865
|
+
const folderId = await getBackupFolder(token, rootFolder, subfolder);
|
|
4866
|
+
return { folderId, token };
|
|
4867
|
+
} catch (err) {
|
|
4868
|
+
if (isAuthError2(err)) {
|
|
4869
|
+
try {
|
|
4870
|
+
const newToken = await requestDriveAccess();
|
|
4871
|
+
const folderId = await getBackupFolder(newToken, rootFolder, subfolder);
|
|
4872
|
+
return { folderId, token: newToken };
|
|
4873
|
+
} catch {
|
|
4874
|
+
return null;
|
|
4875
|
+
}
|
|
4876
|
+
}
|
|
4877
|
+
throw err;
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
async function pushConversationToDrive(database, conversationId, userAddress, token, deps, rootFolder = DEFAULT_ROOT_FOLDER, subfolder = DEFAULT_CONVERSATIONS_FOLDER, _retried = false) {
|
|
4881
|
+
try {
|
|
4882
|
+
await deps.requestEncryptionKey(userAddress);
|
|
4883
|
+
const folderResult = await getConversationsFolder(token, deps.requestDriveAccess, rootFolder, subfolder);
|
|
4884
|
+
if (!folderResult) return "failed";
|
|
4885
|
+
const { folderId, token: activeToken } = folderResult;
|
|
4886
|
+
const filename = `${conversationId}.json`;
|
|
4887
|
+
const existingFile = await findDriveFile(activeToken, folderId, filename);
|
|
4888
|
+
if (existingFile) {
|
|
4889
|
+
const { Q: Q4 } = await import("@nozbe/watermelondb");
|
|
4890
|
+
const conversationsCollection = database.get("conversations");
|
|
4891
|
+
const records = await conversationsCollection.query(Q4.where("conversation_id", conversationId)).fetch();
|
|
4892
|
+
if (records.length > 0) {
|
|
4893
|
+
const conversation = conversationToStored(records[0]);
|
|
4894
|
+
const localUpdated = conversation.updatedAt.getTime();
|
|
4895
|
+
const remoteModified = new Date(existingFile.modifiedTime).getTime();
|
|
4896
|
+
if (localUpdated <= remoteModified) {
|
|
4897
|
+
return "skipped";
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
const exportResult = await deps.exportConversation(conversationId, userAddress);
|
|
4902
|
+
if (!exportResult.success || !exportResult.blob) {
|
|
4903
|
+
return "failed";
|
|
4904
|
+
}
|
|
4905
|
+
if (existingFile) {
|
|
4906
|
+
await updateDriveFile(activeToken, existingFile.id, exportResult.blob);
|
|
4907
|
+
} else {
|
|
4908
|
+
await uploadFileToDrive(activeToken, folderId, exportResult.blob, filename);
|
|
4909
|
+
}
|
|
4910
|
+
return "uploaded";
|
|
4911
|
+
} catch (err) {
|
|
4912
|
+
if (isAuthError2(err) && !_retried) {
|
|
4913
|
+
try {
|
|
4914
|
+
const newToken = await deps.requestDriveAccess();
|
|
4915
|
+
return pushConversationToDrive(database, conversationId, userAddress, newToken, deps, rootFolder, subfolder, true);
|
|
4916
|
+
} catch {
|
|
4917
|
+
return "failed";
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
return "failed";
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
async function performGoogleDriveExport(database, userAddress, token, deps, onProgress, rootFolder = DEFAULT_ROOT_FOLDER, subfolder = DEFAULT_CONVERSATIONS_FOLDER) {
|
|
4924
|
+
await deps.requestEncryptionKey(userAddress);
|
|
4925
|
+
const folderResult = await getConversationsFolder(token, deps.requestDriveAccess, rootFolder, subfolder);
|
|
4926
|
+
if (!folderResult) {
|
|
4927
|
+
return { success: false, uploaded: 0, skipped: 0, total: 0 };
|
|
4928
|
+
}
|
|
4929
|
+
const { token: activeToken } = folderResult;
|
|
4930
|
+
const { Q: Q4 } = await import("@nozbe/watermelondb");
|
|
4931
|
+
const conversationsCollection = database.get("conversations");
|
|
4932
|
+
const records = await conversationsCollection.query(Q4.where("is_deleted", false)).fetch();
|
|
4933
|
+
const conversations = records.map(conversationToStored);
|
|
4934
|
+
const total = conversations.length;
|
|
4935
|
+
if (total === 0) {
|
|
4936
|
+
return { success: true, uploaded: 0, skipped: 0, total: 0 };
|
|
4937
|
+
}
|
|
4938
|
+
let uploaded = 0;
|
|
4939
|
+
let skipped = 0;
|
|
4940
|
+
for (let i = 0; i < conversations.length; i++) {
|
|
4941
|
+
const conv = conversations[i];
|
|
4942
|
+
onProgress?.(i + 1, total);
|
|
4943
|
+
const result = await pushConversationToDrive(
|
|
4944
|
+
database,
|
|
4945
|
+
conv.conversationId,
|
|
4946
|
+
userAddress,
|
|
4947
|
+
activeToken,
|
|
4948
|
+
deps,
|
|
4949
|
+
rootFolder,
|
|
4950
|
+
subfolder
|
|
4951
|
+
);
|
|
4952
|
+
if (result === "uploaded") uploaded++;
|
|
4953
|
+
if (result === "skipped") skipped++;
|
|
4954
|
+
}
|
|
4955
|
+
return { success: true, uploaded, skipped, total };
|
|
4956
|
+
}
|
|
4957
|
+
async function performGoogleDriveImport(userAddress, token, deps, onProgress, rootFolder = DEFAULT_ROOT_FOLDER, subfolder = DEFAULT_CONVERSATIONS_FOLDER) {
|
|
4958
|
+
await deps.requestEncryptionKey(userAddress);
|
|
4959
|
+
const folderResult = await getConversationsFolder(token, deps.requestDriveAccess, rootFolder, subfolder);
|
|
4960
|
+
if (!folderResult) {
|
|
4961
|
+
return { success: false, restored: 0, failed: 0, total: 0, noBackupsFound: true };
|
|
4962
|
+
}
|
|
4963
|
+
const { folderId, token: activeToken } = folderResult;
|
|
4964
|
+
const remoteFiles = await listDriveFiles(activeToken, folderId);
|
|
4965
|
+
if (remoteFiles.length === 0) {
|
|
4966
|
+
return { success: false, restored: 0, failed: 0, total: 0, noBackupsFound: true };
|
|
4967
|
+
}
|
|
4968
|
+
const jsonFiles = remoteFiles.filter((file) => file.name.endsWith(".json"));
|
|
4969
|
+
const total = jsonFiles.length;
|
|
4970
|
+
let restored = 0;
|
|
4971
|
+
let failed = 0;
|
|
4972
|
+
for (let i = 0; i < jsonFiles.length; i++) {
|
|
4973
|
+
const file = jsonFiles[i];
|
|
4974
|
+
onProgress?.(i + 1, total);
|
|
4975
|
+
try {
|
|
4976
|
+
const blob = await downloadDriveFile(activeToken, file.id);
|
|
4977
|
+
const result = await deps.importConversation(blob, userAddress);
|
|
4978
|
+
if (result.success) {
|
|
4979
|
+
restored++;
|
|
4980
|
+
} else {
|
|
4981
|
+
failed++;
|
|
4982
|
+
}
|
|
4983
|
+
} catch {
|
|
4984
|
+
failed++;
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
return { success: true, restored, failed, total };
|
|
4988
|
+
}
|
|
4989
|
+
|
|
4990
|
+
// src/react/useGoogleDriveBackup.ts
|
|
4991
|
+
function useGoogleDriveBackup(options) {
|
|
4992
|
+
const {
|
|
4993
|
+
database,
|
|
4994
|
+
userAddress,
|
|
4995
|
+
accessToken,
|
|
4996
|
+
requestDriveAccess,
|
|
4997
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
4998
|
+
exportConversation,
|
|
4999
|
+
importConversation,
|
|
5000
|
+
rootFolder = DEFAULT_ROOT_FOLDER,
|
|
5001
|
+
conversationsFolder = DEFAULT_CONVERSATIONS_FOLDER
|
|
5002
|
+
} = options;
|
|
5003
|
+
const deps = useMemo5(
|
|
5004
|
+
() => ({
|
|
5005
|
+
requestDriveAccess,
|
|
5006
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
5007
|
+
exportConversation,
|
|
5008
|
+
importConversation
|
|
5009
|
+
}),
|
|
5010
|
+
[
|
|
5011
|
+
requestDriveAccess,
|
|
5012
|
+
requestEncryptionKey2,
|
|
5013
|
+
exportConversation,
|
|
5014
|
+
importConversation
|
|
5015
|
+
]
|
|
5016
|
+
);
|
|
5017
|
+
const ensureToken = useCallback12(async () => {
|
|
5018
|
+
if (accessToken) return accessToken;
|
|
5019
|
+
try {
|
|
5020
|
+
return await requestDriveAccess();
|
|
5021
|
+
} catch {
|
|
5022
|
+
return null;
|
|
5023
|
+
}
|
|
5024
|
+
}, [accessToken, requestDriveAccess]);
|
|
5025
|
+
const backup = useCallback12(
|
|
5026
|
+
async (backupOptions) => {
|
|
5027
|
+
if (!userAddress) {
|
|
5028
|
+
return { error: "Please sign in to backup to Google Drive" };
|
|
5029
|
+
}
|
|
5030
|
+
const token = await ensureToken();
|
|
5031
|
+
if (!token) {
|
|
5032
|
+
return { error: "Google Drive access denied" };
|
|
5033
|
+
}
|
|
5034
|
+
try {
|
|
5035
|
+
return await performGoogleDriveExport(
|
|
5036
|
+
database,
|
|
5037
|
+
userAddress,
|
|
5038
|
+
token,
|
|
5039
|
+
deps,
|
|
5040
|
+
backupOptions?.onProgress,
|
|
5041
|
+
rootFolder,
|
|
5042
|
+
conversationsFolder
|
|
5043
|
+
);
|
|
5044
|
+
} catch (err) {
|
|
5045
|
+
return {
|
|
5046
|
+
error: err instanceof Error ? err.message : "Failed to backup to Google Drive"
|
|
5047
|
+
};
|
|
5048
|
+
}
|
|
5049
|
+
},
|
|
5050
|
+
[database, userAddress, ensureToken, deps, rootFolder, conversationsFolder]
|
|
5051
|
+
);
|
|
5052
|
+
const restore = useCallback12(
|
|
5053
|
+
async (restoreOptions) => {
|
|
5054
|
+
if (!userAddress) {
|
|
5055
|
+
return { error: "Please sign in to restore from Google Drive" };
|
|
5056
|
+
}
|
|
5057
|
+
const token = await ensureToken();
|
|
5058
|
+
if (!token) {
|
|
5059
|
+
return { error: "Google Drive access denied" };
|
|
5060
|
+
}
|
|
5061
|
+
try {
|
|
5062
|
+
return await performGoogleDriveImport(
|
|
5063
|
+
userAddress,
|
|
5064
|
+
token,
|
|
5065
|
+
deps,
|
|
5066
|
+
restoreOptions?.onProgress,
|
|
5067
|
+
rootFolder,
|
|
5068
|
+
conversationsFolder
|
|
5069
|
+
);
|
|
5070
|
+
} catch (err) {
|
|
5071
|
+
return {
|
|
5072
|
+
error: err instanceof Error ? err.message : "Failed to restore from Google Drive"
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
},
|
|
5076
|
+
[userAddress, ensureToken, deps, rootFolder, conversationsFolder]
|
|
5077
|
+
);
|
|
5078
|
+
return {
|
|
5079
|
+
backup,
|
|
5080
|
+
restore,
|
|
5081
|
+
isAuthenticated: !!accessToken
|
|
5082
|
+
};
|
|
5083
|
+
}
|
|
4228
5084
|
export {
|
|
4229
5085
|
Conversation as ChatConversation,
|
|
4230
5086
|
Message as ChatMessage,
|
|
5087
|
+
DEFAULT_BACKUP_FOLDER,
|
|
5088
|
+
DEFAULT_CONVERSATIONS_FOLDER as DEFAULT_DRIVE_CONVERSATIONS_FOLDER,
|
|
5089
|
+
DEFAULT_ROOT_FOLDER as DEFAULT_DRIVE_ROOT_FOLDER,
|
|
4231
5090
|
DEFAULT_TOOL_SELECTOR_MODEL,
|
|
5091
|
+
DropboxAuthProvider,
|
|
4232
5092
|
Memory as StoredMemoryModel,
|
|
4233
5093
|
ModelPreference as StoredModelPreferenceModel,
|
|
4234
5094
|
chatStorageMigrations,
|
|
4235
5095
|
chatStorageSchema,
|
|
5096
|
+
clearToken as clearDropboxToken,
|
|
4236
5097
|
createMemoryContextSystemMessage,
|
|
4237
5098
|
decryptData,
|
|
4238
5099
|
decryptDataBytes,
|
|
@@ -4243,14 +5104,19 @@ export {
|
|
|
4243
5104
|
generateCompositeKey,
|
|
4244
5105
|
generateConversationId,
|
|
4245
5106
|
generateUniqueKey,
|
|
5107
|
+
getStoredToken as getDropboxToken,
|
|
4246
5108
|
hasEncryptionKey,
|
|
4247
5109
|
memoryStorageSchema,
|
|
4248
5110
|
requestEncryptionKey,
|
|
4249
5111
|
selectTool,
|
|
4250
5112
|
settingsStorageSchema,
|
|
5113
|
+
storeToken as storeDropboxToken,
|
|
4251
5114
|
useChat,
|
|
4252
5115
|
useChatStorage,
|
|
5116
|
+
useDropboxAuth,
|
|
5117
|
+
useDropboxBackup,
|
|
4253
5118
|
useEncryption,
|
|
5119
|
+
useGoogleDriveBackup,
|
|
4254
5120
|
useImageGeneration,
|
|
4255
5121
|
useMemoryStorage,
|
|
4256
5122
|
useModels,
|