@reverbia/sdk 1.0.0-next.20251217134403 → 1.0.0-next.20251217144909

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.
@@ -26,17 +26,30 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var __decorateClass = (decorators, target, key, kind) => {
30
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
31
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
32
+ if (decorator = decorators[i])
33
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
34
+ if (kind && result) __defProp(target, key, result);
35
+ return result;
36
+ };
29
37
 
30
38
  // src/react/index.ts
31
39
  var index_exports = {};
32
40
  __export(index_exports, {
33
41
  ChatConversation: () => Conversation,
34
42
  ChatMessage: () => Message,
43
+ DEFAULT_BACKUP_FOLDER: () => DEFAULT_BACKUP_FOLDER,
44
+ DEFAULT_DRIVE_CONVERSATIONS_FOLDER: () => DEFAULT_CONVERSATIONS_FOLDER,
45
+ DEFAULT_DRIVE_ROOT_FOLDER: () => DEFAULT_ROOT_FOLDER,
35
46
  DEFAULT_TOOL_SELECTOR_MODEL: () => DEFAULT_TOOL_SELECTOR_MODEL,
47
+ DropboxAuthProvider: () => DropboxAuthProvider,
36
48
  StoredMemoryModel: () => Memory,
37
49
  StoredModelPreferenceModel: () => ModelPreference,
38
50
  chatStorageMigrations: () => chatStorageMigrations,
39
51
  chatStorageSchema: () => chatStorageSchema,
52
+ clearDropboxToken: () => clearToken,
40
53
  createMemoryContextSystemMessage: () => createMemoryContextSystemMessage,
41
54
  decryptData: () => decryptData,
42
55
  decryptDataBytes: () => decryptDataBytes,
@@ -47,14 +60,19 @@ __export(index_exports, {
47
60
  generateCompositeKey: () => generateCompositeKey,
48
61
  generateConversationId: () => generateConversationId,
49
62
  generateUniqueKey: () => generateUniqueKey,
63
+ getDropboxToken: () => getStoredToken,
50
64
  hasEncryptionKey: () => hasEncryptionKey,
51
65
  memoryStorageSchema: () => memoryStorageSchema,
52
66
  requestEncryptionKey: () => requestEncryptionKey,
53
67
  selectTool: () => selectTool,
54
68
  settingsStorageSchema: () => settingsStorageSchema,
69
+ storeDropboxToken: () => storeToken,
55
70
  useChat: () => useChat,
56
71
  useChatStorage: () => useChatStorage,
72
+ useDropboxAuth: () => useDropboxAuth,
73
+ useDropboxBackup: () => useDropboxBackup,
57
74
  useEncryption: () => useEncryption,
75
+ useGoogleDriveBackup: () => useGoogleDriveBackup,
58
76
  useImageGeneration: () => useImageGeneration,
59
77
  useMemoryStorage: () => useMemoryStorage,
60
78
  useModels: () => useModels,
@@ -1058,11 +1076,11 @@ async function generateLocalChatCompletion(messages, options = {}) {
1058
1076
  });
1059
1077
  this.cb = cb;
1060
1078
  }
1061
- on_finalized_text(text) {
1079
+ on_finalized_text(text4) {
1062
1080
  if (signal?.aborted) {
1063
1081
  throw new Error("AbortError");
1064
1082
  }
1065
- this.cb(text);
1083
+ this.cb(text4);
1066
1084
  }
1067
1085
  }
1068
1086
  const streamer = onToken ? new CallbackStreamer(chatPipeline.tokenizer, onToken) : void 0;
@@ -1736,7 +1754,134 @@ function useEncryption(signMessage) {
1736
1754
  // src/react/useChatStorage.ts
1737
1755
  var import_react2 = require("react");
1738
1756
 
1739
- // src/lib/chatStorage/types.ts
1757
+ // src/lib/db/chat/schema.ts
1758
+ var import_watermelondb = require("@nozbe/watermelondb");
1759
+ var import_migrations = require("@nozbe/watermelondb/Schema/migrations");
1760
+ var chatStorageSchema = (0, import_watermelondb.appSchema)({
1761
+ version: 2,
1762
+ tables: [
1763
+ (0, import_watermelondb.tableSchema)({
1764
+ name: "history",
1765
+ columns: [
1766
+ { name: "message_id", type: "number" },
1767
+ { name: "conversation_id", type: "string", isIndexed: true },
1768
+ { name: "role", type: "string", isIndexed: true },
1769
+ { name: "content", type: "string" },
1770
+ { name: "model", type: "string", isOptional: true },
1771
+ { name: "files", type: "string", isOptional: true },
1772
+ { name: "created_at", type: "number", isIndexed: true },
1773
+ { name: "updated_at", type: "number" },
1774
+ { name: "vector", type: "string", isOptional: true },
1775
+ { name: "embedding_model", type: "string", isOptional: true },
1776
+ { name: "usage", type: "string", isOptional: true },
1777
+ { name: "sources", type: "string", isOptional: true },
1778
+ { name: "response_duration", type: "number", isOptional: true },
1779
+ { name: "was_stopped", type: "boolean", isOptional: true }
1780
+ ]
1781
+ }),
1782
+ (0, import_watermelondb.tableSchema)({
1783
+ name: "conversations",
1784
+ columns: [
1785
+ { name: "conversation_id", type: "string", isIndexed: true },
1786
+ { name: "title", type: "string" },
1787
+ { name: "created_at", type: "number" },
1788
+ { name: "updated_at", type: "number" },
1789
+ { name: "is_deleted", type: "boolean", isIndexed: true }
1790
+ ]
1791
+ })
1792
+ ]
1793
+ });
1794
+ var chatStorageMigrations = (0, import_migrations.schemaMigrations)({
1795
+ migrations: [
1796
+ {
1797
+ toVersion: 2,
1798
+ steps: [
1799
+ (0, import_migrations.addColumns)({
1800
+ table: "history",
1801
+ columns: [{ name: "was_stopped", type: "boolean", isOptional: true }]
1802
+ })
1803
+ ]
1804
+ }
1805
+ ]
1806
+ });
1807
+
1808
+ // src/lib/db/chat/models.ts
1809
+ var import_watermelondb2 = require("@nozbe/watermelondb");
1810
+ var import_decorators = require("@nozbe/watermelondb/decorators");
1811
+ var Message = class extends import_watermelondb2.Model {
1812
+ };
1813
+ Message.table = "history";
1814
+ Message.associations = {
1815
+ conversations: { type: "belongs_to", key: "conversation_id" }
1816
+ };
1817
+ __decorateClass([
1818
+ (0, import_decorators.field)("message_id")
1819
+ ], Message.prototype, "messageId", 2);
1820
+ __decorateClass([
1821
+ (0, import_decorators.text)("conversation_id")
1822
+ ], Message.prototype, "conversationId", 2);
1823
+ __decorateClass([
1824
+ (0, import_decorators.text)("role")
1825
+ ], Message.prototype, "role", 2);
1826
+ __decorateClass([
1827
+ (0, import_decorators.text)("content")
1828
+ ], Message.prototype, "content", 2);
1829
+ __decorateClass([
1830
+ (0, import_decorators.text)("model")
1831
+ ], Message.prototype, "model", 2);
1832
+ __decorateClass([
1833
+ (0, import_decorators.json)("files", (json3) => json3)
1834
+ ], Message.prototype, "files", 2);
1835
+ __decorateClass([
1836
+ (0, import_decorators.date)("created_at")
1837
+ ], Message.prototype, "createdAt", 2);
1838
+ __decorateClass([
1839
+ (0, import_decorators.date)("updated_at")
1840
+ ], Message.prototype, "updatedAt", 2);
1841
+ __decorateClass([
1842
+ (0, import_decorators.json)("vector", (json3) => json3)
1843
+ ], Message.prototype, "vector", 2);
1844
+ __decorateClass([
1845
+ (0, import_decorators.text)("embedding_model")
1846
+ ], Message.prototype, "embeddingModel", 2);
1847
+ __decorateClass([
1848
+ (0, import_decorators.json)("usage", (json3) => json3)
1849
+ ], Message.prototype, "usage", 2);
1850
+ __decorateClass([
1851
+ (0, import_decorators.json)("sources", (json3) => json3)
1852
+ ], Message.prototype, "sources", 2);
1853
+ __decorateClass([
1854
+ (0, import_decorators.field)("response_duration")
1855
+ ], Message.prototype, "responseDuration", 2);
1856
+ __decorateClass([
1857
+ (0, import_decorators.field)("was_stopped")
1858
+ ], Message.prototype, "wasStopped", 2);
1859
+ var Conversation = class extends import_watermelondb2.Model {
1860
+ };
1861
+ Conversation.table = "conversations";
1862
+ Conversation.associations = {
1863
+ history: { type: "has_many", foreignKey: "conversation_id" }
1864
+ };
1865
+ __decorateClass([
1866
+ (0, import_decorators.text)("conversation_id")
1867
+ ], Conversation.prototype, "conversationId", 2);
1868
+ __decorateClass([
1869
+ (0, import_decorators.text)("title")
1870
+ ], Conversation.prototype, "title", 2);
1871
+ __decorateClass([
1872
+ (0, import_decorators.date)("created_at")
1873
+ ], Conversation.prototype, "createdAt", 2);
1874
+ __decorateClass([
1875
+ (0, import_decorators.date)("updated_at")
1876
+ ], Conversation.prototype, "updatedAt", 2);
1877
+ __decorateClass([
1878
+ (0, import_decorators.field)("is_deleted")
1879
+ ], Conversation.prototype, "isDeleted", 2);
1880
+
1881
+ // src/lib/db/chat/types.ts
1882
+ function generateConversationId() {
1883
+ return `conv_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1884
+ }
1740
1885
  function convertUsageToStored(usage) {
1741
1886
  if (!usage) return void 0;
1742
1887
  return {
@@ -1746,12 +1891,9 @@ function convertUsageToStored(usage) {
1746
1891
  costMicroUsd: usage.cost_micro_usd
1747
1892
  };
1748
1893
  }
1749
- function generateConversationId() {
1750
- return `conv_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1751
- }
1752
1894
 
1753
- // src/lib/chatStorage/operations.ts
1754
- var import_watermelondb = require("@nozbe/watermelondb");
1895
+ // src/lib/db/chat/operations.ts
1896
+ var import_watermelondb3 = require("@nozbe/watermelondb");
1755
1897
  function messageToStored(message) {
1756
1898
  return {
1757
1899
  uniqueId: message.id,
@@ -1794,15 +1936,15 @@ async function createConversationOp(ctx, opts, defaultTitle = "New Conversation"
1794
1936
  return conversationToStored(created);
1795
1937
  }
1796
1938
  async function getConversationOp(ctx, id) {
1797
- const results = await ctx.conversationsCollection.query(import_watermelondb.Q.where("conversation_id", id), import_watermelondb.Q.where("is_deleted", false)).fetch();
1939
+ const results = await ctx.conversationsCollection.query(import_watermelondb3.Q.where("conversation_id", id), import_watermelondb3.Q.where("is_deleted", false)).fetch();
1798
1940
  return results.length > 0 ? conversationToStored(results[0]) : null;
1799
1941
  }
1800
1942
  async function getConversationsOp(ctx) {
1801
- const results = await ctx.conversationsCollection.query(import_watermelondb.Q.where("is_deleted", false), import_watermelondb.Q.sortBy("created_at", import_watermelondb.Q.desc)).fetch();
1943
+ const results = await ctx.conversationsCollection.query(import_watermelondb3.Q.where("is_deleted", false), import_watermelondb3.Q.sortBy("created_at", import_watermelondb3.Q.desc)).fetch();
1802
1944
  return results.map(conversationToStored);
1803
1945
  }
1804
1946
  async function updateConversationTitleOp(ctx, id, title) {
1805
- const results = await ctx.conversationsCollection.query(import_watermelondb.Q.where("conversation_id", id), import_watermelondb.Q.where("is_deleted", false)).fetch();
1947
+ const results = await ctx.conversationsCollection.query(import_watermelondb3.Q.where("conversation_id", id), import_watermelondb3.Q.where("is_deleted", false)).fetch();
1806
1948
  if (results.length > 0) {
1807
1949
  await ctx.database.write(async () => {
1808
1950
  await results[0].update((conv) => {
@@ -1814,7 +1956,7 @@ async function updateConversationTitleOp(ctx, id, title) {
1814
1956
  return false;
1815
1957
  }
1816
1958
  async function deleteConversationOp(ctx, id) {
1817
- const results = await ctx.conversationsCollection.query(import_watermelondb.Q.where("conversation_id", id), import_watermelondb.Q.where("is_deleted", false)).fetch();
1959
+ const results = await ctx.conversationsCollection.query(import_watermelondb3.Q.where("conversation_id", id), import_watermelondb3.Q.where("is_deleted", false)).fetch();
1818
1960
  if (results.length > 0) {
1819
1961
  await ctx.database.write(async () => {
1820
1962
  await results[0].update((conv) => {
@@ -1826,14 +1968,14 @@ async function deleteConversationOp(ctx, id) {
1826
1968
  return false;
1827
1969
  }
1828
1970
  async function getMessagesOp(ctx, convId) {
1829
- const results = await ctx.messagesCollection.query(import_watermelondb.Q.where("conversation_id", convId), import_watermelondb.Q.sortBy("message_id", import_watermelondb.Q.asc)).fetch();
1971
+ const results = await ctx.messagesCollection.query(import_watermelondb3.Q.where("conversation_id", convId), import_watermelondb3.Q.sortBy("message_id", import_watermelondb3.Q.asc)).fetch();
1830
1972
  return results.map(messageToStored);
1831
1973
  }
1832
1974
  async function getMessageCountOp(ctx, convId) {
1833
- return await ctx.messagesCollection.query(import_watermelondb.Q.where("conversation_id", convId)).fetchCount();
1975
+ return await ctx.messagesCollection.query(import_watermelondb3.Q.where("conversation_id", convId)).fetchCount();
1834
1976
  }
1835
1977
  async function clearMessagesOp(ctx, convId) {
1836
- const messages = await ctx.messagesCollection.query(import_watermelondb.Q.where("conversation_id", convId)).fetch();
1978
+ const messages = await ctx.messagesCollection.query(import_watermelondb3.Q.where("conversation_id", convId)).fetch();
1837
1979
  await ctx.database.write(async () => {
1838
1980
  for (const message of messages) {
1839
1981
  await message.destroyPermanently();
@@ -1892,11 +2034,11 @@ function cosineSimilarity(a, b) {
1892
2034
  }
1893
2035
  async function searchMessagesOp(ctx, queryVector, options) {
1894
2036
  const { limit = 10, minSimilarity = 0.5, conversationId } = options || {};
1895
- const activeConversations = await ctx.conversationsCollection.query(import_watermelondb.Q.where("is_deleted", false)).fetch();
2037
+ const activeConversations = await ctx.conversationsCollection.query(import_watermelondb3.Q.where("is_deleted", false)).fetch();
1896
2038
  const activeConversationIds = new Set(
1897
2039
  activeConversations.map((c) => c.conversationId)
1898
2040
  );
1899
- const queryConditions = conversationId ? [import_watermelondb.Q.where("conversation_id", conversationId)] : [];
2041
+ const queryConditions = conversationId ? [import_watermelondb3.Q.where("conversation_id", conversationId)] : [];
1900
2042
  const messages = await ctx.messagesCollection.query(...queryConditions).fetch();
1901
2043
  const resultsWithSimilarity = [];
1902
2044
  for (const message of messages) {
@@ -2256,191 +2398,87 @@ function useChatStorage(options) {
2256
2398
  };
2257
2399
  }
2258
2400
 
2259
- // src/lib/chatStorage/schema.ts
2260
- var import_watermelondb2 = require("@nozbe/watermelondb");
2261
- var import_migrations = require("@nozbe/watermelondb/Schema/migrations");
2262
- var chatStorageSchema = (0, import_watermelondb2.appSchema)({
2263
- version: 2,
2401
+ // src/react/useMemoryStorage.ts
2402
+ var import_react3 = require("react");
2403
+ var import_client6 = require("@reverbia/sdk");
2404
+
2405
+ // src/lib/db/memory/schema.ts
2406
+ var import_watermelondb4 = require("@nozbe/watermelondb");
2407
+ var memoryStorageSchema = (0, import_watermelondb4.appSchema)({
2408
+ version: 1,
2264
2409
  tables: [
2265
- (0, import_watermelondb2.tableSchema)({
2266
- name: "history",
2410
+ (0, import_watermelondb4.tableSchema)({
2411
+ name: "memories",
2267
2412
  columns: [
2268
- { name: "message_id", type: "number" },
2269
- // Sequential ID within conversation
2270
- { name: "conversation_id", type: "string", isIndexed: true },
2271
- { name: "role", type: "string", isIndexed: true },
2272
- // 'user' | 'assistant' | 'system'
2273
- { name: "content", type: "string" },
2274
- { name: "model", type: "string", isOptional: true },
2275
- { name: "files", type: "string", isOptional: true },
2276
- // JSON stringified FileMetadata[]
2413
+ { name: "type", type: "string", isIndexed: true },
2414
+ { name: "namespace", type: "string", isIndexed: true },
2415
+ { name: "key", type: "string", isIndexed: true },
2416
+ { name: "value", type: "string" },
2417
+ { name: "raw_evidence", type: "string" },
2418
+ { name: "confidence", type: "number" },
2419
+ { name: "pii", type: "boolean", isIndexed: true },
2420
+ { name: "composite_key", type: "string", isIndexed: true },
2421
+ { name: "unique_key", type: "string", isIndexed: true },
2277
2422
  { name: "created_at", type: "number", isIndexed: true },
2278
2423
  { name: "updated_at", type: "number" },
2279
- { name: "vector", type: "string", isOptional: true },
2280
- // JSON stringified number[]
2424
+ { name: "embedding", type: "string", isOptional: true },
2281
2425
  { name: "embedding_model", type: "string", isOptional: true },
2282
- { name: "usage", type: "string", isOptional: true },
2283
- // JSON stringified ChatCompletionUsage
2284
- { name: "sources", type: "string", isOptional: true },
2285
- // JSON stringified SearchSource[]
2286
- { name: "response_duration", type: "number", isOptional: true },
2287
- { name: "was_stopped", type: "boolean", isOptional: true }
2288
- ]
2289
- }),
2290
- (0, import_watermelondb2.tableSchema)({
2291
- name: "conversations",
2292
- columns: [
2293
- { name: "conversation_id", type: "string", isIndexed: true },
2294
- { name: "title", type: "string" },
2295
- { name: "created_at", type: "number" },
2296
- { name: "updated_at", type: "number" },
2297
2426
  { name: "is_deleted", type: "boolean", isIndexed: true }
2298
2427
  ]
2299
2428
  })
2300
2429
  ]
2301
2430
  });
2302
- var chatStorageMigrations = (0, import_migrations.schemaMigrations)({
2303
- migrations: [
2304
- {
2305
- toVersion: 2,
2306
- steps: [
2307
- (0, import_migrations.addColumns)({
2308
- table: "history",
2309
- columns: [
2310
- { name: "was_stopped", type: "boolean", isOptional: true }
2311
- ]
2312
- })
2313
- ]
2314
- }
2315
- ]
2316
- });
2317
2431
 
2318
- // src/lib/chatStorage/models.ts
2319
- var import_watermelondb3 = require("@nozbe/watermelondb");
2320
- var Message = class extends import_watermelondb3.Model {
2321
- /** Sequential message ID within conversation */
2322
- get messageId() {
2323
- return this._getRaw("message_id");
2324
- }
2325
- /** Links message to its conversation */
2326
- get conversationId() {
2327
- return this._getRaw("conversation_id");
2328
- }
2329
- /** Who sent the message: 'user' | 'assistant' | 'system' */
2330
- get role() {
2331
- return this._getRaw("role");
2332
- }
2333
- /** The message text content */
2334
- get content() {
2335
- return this._getRaw("content");
2336
- }
2337
- /** LLM model used (e.g., GPT-4, Claude) */
2338
- get model() {
2339
- const value = this._getRaw("model");
2340
- return value ? value : void 0;
2341
- }
2342
- /** Optional attached files */
2343
- get files() {
2344
- const raw = this._getRaw("files");
2345
- if (!raw) return void 0;
2346
- try {
2347
- return JSON.parse(raw);
2348
- } catch {
2349
- return void 0;
2350
- }
2351
- }
2352
- /** Created timestamp */
2353
- get createdAt() {
2354
- return new Date(this._getRaw("created_at"));
2355
- }
2356
- /** Updated timestamp */
2357
- get updatedAt() {
2358
- return new Date(this._getRaw("updated_at"));
2359
- }
2360
- /** Embedding vector for semantic search */
2361
- get vector() {
2362
- const raw = this._getRaw("vector");
2363
- if (!raw) return void 0;
2364
- try {
2365
- return JSON.parse(raw);
2366
- } catch {
2367
- return void 0;
2368
- }
2369
- }
2370
- /** Model used to generate embedding */
2371
- get embeddingModel() {
2372
- const value = this._getRaw("embedding_model");
2373
- return value ? value : void 0;
2374
- }
2375
- /** Token counts and cost */
2376
- get usage() {
2377
- const raw = this._getRaw("usage");
2378
- if (!raw) return void 0;
2379
- try {
2380
- return JSON.parse(raw);
2381
- } catch {
2382
- return void 0;
2383
- }
2384
- }
2385
- /** Web search sources */
2386
- get sources() {
2387
- const raw = this._getRaw("sources");
2388
- if (!raw) return void 0;
2389
- try {
2390
- return JSON.parse(raw);
2391
- } catch {
2392
- return void 0;
2393
- }
2394
- }
2395
- /** Response time in seconds */
2396
- get responseDuration() {
2397
- const value = this._getRaw("response_duration");
2398
- return value !== null && value !== void 0 ? value : void 0;
2399
- }
2400
- /** Whether the message generation was stopped by the user */
2401
- get wasStopped() {
2402
- return this._getRaw("was_stopped");
2403
- }
2404
- };
2405
- Message.table = "history";
2406
- Message.associations = {
2407
- conversations: { type: "belongs_to", key: "conversation_id" }
2408
- };
2409
- var Conversation = class extends import_watermelondb3.Model {
2410
- /** Unique conversation identifier */
2411
- get conversationId() {
2412
- return this._getRaw("conversation_id");
2413
- }
2414
- /** Conversation title */
2415
- get title() {
2416
- return this._getRaw("title");
2417
- }
2418
- /** Created timestamp */
2419
- get createdAt() {
2420
- return new Date(this._getRaw("created_at"));
2421
- }
2422
- /** Updated timestamp */
2423
- get updatedAt() {
2424
- return new Date(this._getRaw("updated_at"));
2425
- }
2426
- /** Soft delete flag */
2427
- get isDeleted() {
2428
- return this._getRaw("is_deleted");
2429
- }
2430
- };
2431
- Conversation.table = "conversations";
2432
- Conversation.associations = {
2433
- history: { type: "has_many", foreignKey: "conversation_id" }
2432
+ // src/lib/db/memory/models.ts
2433
+ var import_watermelondb5 = require("@nozbe/watermelondb");
2434
+ var import_decorators2 = require("@nozbe/watermelondb/decorators");
2435
+ var Memory = class extends import_watermelondb5.Model {
2434
2436
  };
2437
+ Memory.table = "memories";
2438
+ __decorateClass([
2439
+ (0, import_decorators2.text)("type")
2440
+ ], Memory.prototype, "type", 2);
2441
+ __decorateClass([
2442
+ (0, import_decorators2.text)("namespace")
2443
+ ], Memory.prototype, "namespace", 2);
2444
+ __decorateClass([
2445
+ (0, import_decorators2.text)("key")
2446
+ ], Memory.prototype, "key", 2);
2447
+ __decorateClass([
2448
+ (0, import_decorators2.text)("value")
2449
+ ], Memory.prototype, "value", 2);
2450
+ __decorateClass([
2451
+ (0, import_decorators2.text)("raw_evidence")
2452
+ ], Memory.prototype, "rawEvidence", 2);
2453
+ __decorateClass([
2454
+ (0, import_decorators2.field)("confidence")
2455
+ ], Memory.prototype, "confidence", 2);
2456
+ __decorateClass([
2457
+ (0, import_decorators2.field)("pii")
2458
+ ], Memory.prototype, "pii", 2);
2459
+ __decorateClass([
2460
+ (0, import_decorators2.text)("composite_key")
2461
+ ], Memory.prototype, "compositeKey", 2);
2462
+ __decorateClass([
2463
+ (0, import_decorators2.text)("unique_key")
2464
+ ], Memory.prototype, "uniqueKey", 2);
2465
+ __decorateClass([
2466
+ (0, import_decorators2.date)("created_at")
2467
+ ], Memory.prototype, "createdAt", 2);
2468
+ __decorateClass([
2469
+ (0, import_decorators2.date)("updated_at")
2470
+ ], Memory.prototype, "updatedAt", 2);
2471
+ __decorateClass([
2472
+ (0, import_decorators2.json)("embedding", (json3) => json3)
2473
+ ], Memory.prototype, "embedding", 2);
2474
+ __decorateClass([
2475
+ (0, import_decorators2.text)("embedding_model")
2476
+ ], Memory.prototype, "embeddingModel", 2);
2477
+ __decorateClass([
2478
+ (0, import_decorators2.field)("is_deleted")
2479
+ ], Memory.prototype, "isDeleted", 2);
2435
2480
 
2436
- // src/react/useMemoryStorage.ts
2437
- var import_react3 = require("react");
2438
- var import_client6 = require("@reverbia/sdk");
2439
-
2440
- // src/lib/memoryStorage/operations.ts
2441
- var import_watermelondb4 = require("@nozbe/watermelondb");
2442
-
2443
- // src/lib/memoryStorage/types.ts
2481
+ // src/lib/db/memory/types.ts
2444
2482
  function generateCompositeKey(namespace, key) {
2445
2483
  return `${namespace}:${key}`;
2446
2484
  }
@@ -2466,7 +2504,8 @@ function cosineSimilarity2(a, b) {
2466
2504
  return dotProduct / denominator;
2467
2505
  }
2468
2506
 
2469
- // src/lib/memoryStorage/operations.ts
2507
+ // src/lib/db/memory/operations.ts
2508
+ var import_watermelondb6 = require("@nozbe/watermelondb");
2470
2509
  function memoryToStored(memory) {
2471
2510
  return {
2472
2511
  uniqueId: memory.id,
@@ -2487,7 +2526,7 @@ function memoryToStored(memory) {
2487
2526
  };
2488
2527
  }
2489
2528
  async function getAllMemoriesOp(ctx) {
2490
- const results = await ctx.memoriesCollection.query(import_watermelondb4.Q.where("is_deleted", false), import_watermelondb4.Q.sortBy("created_at", import_watermelondb4.Q.desc)).fetch();
2529
+ const results = await ctx.memoriesCollection.query(import_watermelondb6.Q.where("is_deleted", false), import_watermelondb6.Q.sortBy("created_at", import_watermelondb6.Q.desc)).fetch();
2491
2530
  return results.map(memoryToStored);
2492
2531
  }
2493
2532
  async function getMemoryByIdOp(ctx, id) {
@@ -2501,18 +2540,18 @@ async function getMemoryByIdOp(ctx, id) {
2501
2540
  }
2502
2541
  async function getMemoriesByNamespaceOp(ctx, namespace) {
2503
2542
  const results = await ctx.memoriesCollection.query(
2504
- import_watermelondb4.Q.where("namespace", namespace),
2505
- import_watermelondb4.Q.where("is_deleted", false),
2506
- import_watermelondb4.Q.sortBy("created_at", import_watermelondb4.Q.desc)
2543
+ import_watermelondb6.Q.where("namespace", namespace),
2544
+ import_watermelondb6.Q.where("is_deleted", false),
2545
+ import_watermelondb6.Q.sortBy("created_at", import_watermelondb6.Q.desc)
2507
2546
  ).fetch();
2508
2547
  return results.map(memoryToStored);
2509
2548
  }
2510
2549
  async function getMemoriesByKeyOp(ctx, namespace, key) {
2511
2550
  const compositeKey = generateCompositeKey(namespace, key);
2512
2551
  const results = await ctx.memoriesCollection.query(
2513
- import_watermelondb4.Q.where("composite_key", compositeKey),
2514
- import_watermelondb4.Q.where("is_deleted", false),
2515
- import_watermelondb4.Q.sortBy("created_at", import_watermelondb4.Q.desc)
2552
+ import_watermelondb6.Q.where("composite_key", compositeKey),
2553
+ import_watermelondb6.Q.where("is_deleted", false),
2554
+ import_watermelondb6.Q.sortBy("created_at", import_watermelondb6.Q.desc)
2516
2555
  ).fetch();
2517
2556
  return results.map(memoryToStored);
2518
2557
  }
@@ -2520,7 +2559,7 @@ async function saveMemoryOp(ctx, opts) {
2520
2559
  const compositeKey = generateCompositeKey(opts.namespace, opts.key);
2521
2560
  const uniqueKey = generateUniqueKey(opts.namespace, opts.key, opts.value);
2522
2561
  const result = await ctx.database.write(async () => {
2523
- const existing = await ctx.memoriesCollection.query(import_watermelondb4.Q.where("unique_key", uniqueKey)).fetch();
2562
+ const existing = await ctx.memoriesCollection.query(import_watermelondb6.Q.where("unique_key", uniqueKey)).fetch();
2524
2563
  if (existing.length > 0) {
2525
2564
  const existingMemory = existing[0];
2526
2565
  const shouldPreserveEmbedding = existingMemory.value === opts.value && existingMemory.rawEvidence === opts.rawEvidence && existingMemory.type === opts.type && existingMemory.namespace === opts.namespace && existingMemory.key === opts.key && existingMemory.embedding !== void 0 && existingMemory.embedding.length > 0 && !opts.embedding;
@@ -2593,7 +2632,7 @@ async function updateMemoryOp(ctx, id, updates) {
2593
2632
  const newCompositeKey = generateCompositeKey(newNamespace, newKey);
2594
2633
  const newUniqueKey = generateUniqueKey(newNamespace, newKey, newValue);
2595
2634
  if (newUniqueKey !== memory.uniqueKey) {
2596
- const existing = await ctx.memoriesCollection.query(import_watermelondb4.Q.where("unique_key", newUniqueKey), import_watermelondb4.Q.where("is_deleted", false)).fetch();
2635
+ const existing = await ctx.memoriesCollection.query(import_watermelondb6.Q.where("unique_key", newUniqueKey), import_watermelondb6.Q.where("is_deleted", false)).fetch();
2597
2636
  if (existing.length > 0) {
2598
2637
  return { ok: false, reason: "conflict", conflictingKey: newUniqueKey };
2599
2638
  }
@@ -2649,7 +2688,7 @@ async function deleteMemoryByIdOp(ctx, id) {
2649
2688
  }
2650
2689
  async function deleteMemoryOp(ctx, namespace, key, value) {
2651
2690
  const uniqueKey = generateUniqueKey(namespace, key, value);
2652
- const results = await ctx.memoriesCollection.query(import_watermelondb4.Q.where("unique_key", uniqueKey)).fetch();
2691
+ const results = await ctx.memoriesCollection.query(import_watermelondb6.Q.where("unique_key", uniqueKey)).fetch();
2653
2692
  if (results.length > 0) {
2654
2693
  await ctx.database.write(async () => {
2655
2694
  await results[0].update((mem) => {
@@ -2660,7 +2699,7 @@ async function deleteMemoryOp(ctx, namespace, key, value) {
2660
2699
  }
2661
2700
  async function deleteMemoriesByKeyOp(ctx, namespace, key) {
2662
2701
  const compositeKey = generateCompositeKey(namespace, key);
2663
- const results = await ctx.memoriesCollection.query(import_watermelondb4.Q.where("composite_key", compositeKey), import_watermelondb4.Q.where("is_deleted", false)).fetch();
2702
+ const results = await ctx.memoriesCollection.query(import_watermelondb6.Q.where("composite_key", compositeKey), import_watermelondb6.Q.where("is_deleted", false)).fetch();
2664
2703
  await ctx.database.write(async () => {
2665
2704
  for (const memory of results) {
2666
2705
  await memory.update((mem) => {
@@ -2670,7 +2709,7 @@ async function deleteMemoriesByKeyOp(ctx, namespace, key) {
2670
2709
  });
2671
2710
  }
2672
2711
  async function clearAllMemoriesOp(ctx) {
2673
- const results = await ctx.memoriesCollection.query(import_watermelondb4.Q.where("is_deleted", false)).fetch();
2712
+ const results = await ctx.memoriesCollection.query(import_watermelondb6.Q.where("is_deleted", false)).fetch();
2674
2713
  await ctx.database.write(async () => {
2675
2714
  for (const memory of results) {
2676
2715
  await memory.update((mem) => {
@@ -2680,7 +2719,7 @@ async function clearAllMemoriesOp(ctx) {
2680
2719
  });
2681
2720
  }
2682
2721
  async function searchSimilarMemoriesOp(ctx, queryEmbedding, limit = 10, minSimilarity = 0.6) {
2683
- const allMemories = await ctx.memoriesCollection.query(import_watermelondb4.Q.where("is_deleted", false)).fetch();
2722
+ const allMemories = await ctx.memoriesCollection.query(import_watermelondb6.Q.where("is_deleted", false)).fetch();
2684
2723
  const memoriesWithEmbeddings = allMemories.filter(
2685
2724
  (m) => m.embedding && m.embedding.length > 0
2686
2725
  );
@@ -2883,7 +2922,7 @@ var DEFAULT_COMPLETION_MODEL = "openai/gpt-4o";
2883
2922
 
2884
2923
  // src/lib/memory/embeddings.ts
2885
2924
  var embeddingPipeline = null;
2886
- var generateEmbeddingForText = async (text, options = {}) => {
2925
+ var generateEmbeddingForText = async (text4, options = {}) => {
2887
2926
  const { baseUrl = BASE_URL, provider = "local" } = options;
2888
2927
  if (provider === "api") {
2889
2928
  const { getToken, model: model2 } = options;
@@ -2897,7 +2936,7 @@ var generateEmbeddingForText = async (text, options = {}) => {
2897
2936
  const response = await postApiV1Embeddings({
2898
2937
  baseUrl,
2899
2938
  body: {
2900
- input: text,
2939
+ input: text4,
2901
2940
  model: model2 ?? DEFAULT_API_EMBEDDING_MODEL
2902
2941
  },
2903
2942
  headers: {
@@ -2923,7 +2962,7 @@ var generateEmbeddingForText = async (text, options = {}) => {
2923
2962
  const { pipeline } = await import("@huggingface/transformers");
2924
2963
  embeddingPipeline = await pipeline("feature-extraction", model);
2925
2964
  }
2926
- const output = await embeddingPipeline(text, {
2965
+ const output = await embeddingPipeline(text4, {
2927
2966
  pooling: "cls",
2928
2967
  normalize: true
2929
2968
  });
@@ -2937,14 +2976,14 @@ var generateEmbeddingForText = async (text, options = {}) => {
2937
2976
  }
2938
2977
  };
2939
2978
  var generateEmbeddingForMemory = async (memory, options = {}) => {
2940
- const text = [
2979
+ const text4 = [
2941
2980
  memory.rawEvidence,
2942
2981
  memory.type,
2943
2982
  memory.namespace,
2944
2983
  memory.key,
2945
2984
  memory.value
2946
2985
  ].filter(Boolean).join(" ");
2947
- return generateEmbeddingForText(text, options);
2986
+ return generateEmbeddingForText(text4, options);
2948
2987
  };
2949
2988
 
2950
2989
  // src/react/useMemoryStorage.ts
@@ -3496,118 +3535,39 @@ function useMemoryStorage(options) {
3496
3535
  };
3497
3536
  }
3498
3537
 
3499
- // src/lib/memoryStorage/schema.ts
3500
- var import_watermelondb5 = require("@nozbe/watermelondb");
3501
- var memoryStorageSchema = (0, import_watermelondb5.appSchema)({
3538
+ // src/react/useSettings.ts
3539
+ var import_react4 = require("react");
3540
+
3541
+ // src/lib/db/settings/schema.ts
3542
+ var import_watermelondb7 = require("@nozbe/watermelondb");
3543
+ var settingsStorageSchema = (0, import_watermelondb7.appSchema)({
3502
3544
  version: 1,
3503
3545
  tables: [
3504
- (0, import_watermelondb5.tableSchema)({
3505
- name: "memories",
3546
+ (0, import_watermelondb7.tableSchema)({
3547
+ name: "modelPreferences",
3506
3548
  columns: [
3507
- // Memory type classification
3508
- { name: "type", type: "string", isIndexed: true },
3509
- // 'identity' | 'preference' | 'project' | 'skill' | 'constraint'
3510
- // Hierarchical key structure
3511
- { name: "namespace", type: "string", isIndexed: true },
3512
- { name: "key", type: "string", isIndexed: true },
3513
- { name: "value", type: "string" },
3514
- // Evidence and confidence
3515
- { name: "raw_evidence", type: "string" },
3516
- { name: "confidence", type: "number" },
3517
- { name: "pii", type: "boolean", isIndexed: true },
3518
- // Composite keys for efficient lookups
3519
- { name: "composite_key", type: "string", isIndexed: true },
3520
- // namespace:key
3521
- { name: "unique_key", type: "string", isIndexed: true },
3522
- // namespace:key:value
3523
- // Timestamps
3524
- { name: "created_at", type: "number", isIndexed: true },
3525
- { name: "updated_at", type: "number" },
3526
- // Vector embeddings for semantic search
3527
- { name: "embedding", type: "string", isOptional: true },
3528
- // JSON stringified number[]
3529
- { name: "embedding_model", type: "string", isOptional: true },
3530
- // Soft delete flag
3531
- { name: "is_deleted", type: "boolean", isIndexed: true }
3549
+ { name: "wallet_address", type: "string", isIndexed: true },
3550
+ { name: "models", type: "string", isOptional: true }
3532
3551
  ]
3533
3552
  })
3534
3553
  ]
3535
3554
  });
3536
3555
 
3537
- // src/lib/memoryStorage/models.ts
3538
- var import_watermelondb6 = require("@nozbe/watermelondb");
3539
- var Memory = class extends import_watermelondb6.Model {
3540
- /** Memory type classification */
3541
- get type() {
3542
- return this._getRaw("type");
3543
- }
3544
- /** Namespace for grouping related memories */
3545
- get namespace() {
3546
- return this._getRaw("namespace");
3547
- }
3548
- /** Key within the namespace */
3549
- get key() {
3550
- return this._getRaw("key");
3551
- }
3552
- /** The memory value/content */
3553
- get value() {
3554
- return this._getRaw("value");
3555
- }
3556
- /** Raw evidence from which this memory was extracted */
3557
- get rawEvidence() {
3558
- return this._getRaw("raw_evidence");
3559
- }
3560
- /** Confidence score (0-1) */
3561
- get confidence() {
3562
- return this._getRaw("confidence");
3563
- }
3564
- /** Whether this memory contains PII */
3565
- get pii() {
3566
- return this._getRaw("pii");
3567
- }
3568
- /** Composite key (namespace:key) for efficient lookups */
3569
- get compositeKey() {
3570
- return this._getRaw("composite_key");
3571
- }
3572
- /** Unique key (namespace:key:value) for deduplication */
3573
- get uniqueKey() {
3574
- return this._getRaw("unique_key");
3575
- }
3576
- /** Created timestamp */
3577
- get createdAt() {
3578
- return new Date(this._getRaw("created_at"));
3579
- }
3580
- /** Updated timestamp */
3581
- get updatedAt() {
3582
- return new Date(this._getRaw("updated_at"));
3583
- }
3584
- /** Embedding vector for semantic search */
3585
- get embedding() {
3586
- const raw = this._getRaw("embedding");
3587
- if (!raw) return void 0;
3588
- try {
3589
- return JSON.parse(raw);
3590
- } catch {
3591
- return void 0;
3592
- }
3593
- }
3594
- /** Model used to generate embedding */
3595
- get embeddingModel() {
3596
- const value = this._getRaw("embedding_model");
3597
- return value ? value : void 0;
3598
- }
3599
- /** Soft delete flag */
3600
- get isDeleted() {
3601
- return this._getRaw("is_deleted");
3602
- }
3556
+ // src/lib/db/settings/models.ts
3557
+ var import_watermelondb8 = require("@nozbe/watermelondb");
3558
+ var import_decorators3 = require("@nozbe/watermelondb/decorators");
3559
+ var ModelPreference = class extends import_watermelondb8.Model {
3603
3560
  };
3604
- Memory.table = "memories";
3605
-
3606
- // src/react/useSettings.ts
3607
- var import_react4 = require("react");
3561
+ ModelPreference.table = "modelPreferences";
3562
+ __decorateClass([
3563
+ (0, import_decorators3.text)("wallet_address")
3564
+ ], ModelPreference.prototype, "walletAddress", 2);
3565
+ __decorateClass([
3566
+ (0, import_decorators3.text)("models")
3567
+ ], ModelPreference.prototype, "models", 2);
3608
3568
 
3609
- // src/lib/settingsStorage/operations.ts
3610
- var import_watermelondb7 = require("@nozbe/watermelondb");
3569
+ // src/lib/db/settings/operations.ts
3570
+ var import_watermelondb9 = require("@nozbe/watermelondb");
3611
3571
  function modelPreferenceToStored(preference) {
3612
3572
  return {
3613
3573
  uniqueId: preference.id,
@@ -3616,12 +3576,12 @@ function modelPreferenceToStored(preference) {
3616
3576
  };
3617
3577
  }
3618
3578
  async function getModelPreferenceOp(ctx, walletAddress) {
3619
- const results = await ctx.modelPreferencesCollection.query(import_watermelondb7.Q.where("wallet_address", walletAddress)).fetch();
3579
+ const results = await ctx.modelPreferencesCollection.query(import_watermelondb9.Q.where("wallet_address", walletAddress)).fetch();
3620
3580
  return results.length > 0 ? modelPreferenceToStored(results[0]) : null;
3621
3581
  }
3622
3582
  async function setModelPreferenceOp(ctx, walletAddress, models) {
3623
3583
  const result = await ctx.database.write(async () => {
3624
- const results = await ctx.modelPreferencesCollection.query(import_watermelondb7.Q.where("wallet_address", walletAddress)).fetch();
3584
+ const results = await ctx.modelPreferencesCollection.query(import_watermelondb9.Q.where("wallet_address", walletAddress)).fetch();
3625
3585
  if (results.length > 0) {
3626
3586
  const preference = results[0];
3627
3587
  await preference.update((pref) => {
@@ -3639,7 +3599,7 @@ async function setModelPreferenceOp(ctx, walletAddress, models) {
3639
3599
  return modelPreferenceToStored(result);
3640
3600
  }
3641
3601
  async function deleteModelPreferenceOp(ctx, walletAddress) {
3642
- const results = await ctx.modelPreferencesCollection.query(import_watermelondb7.Q.where("wallet_address", walletAddress)).fetch();
3602
+ const results = await ctx.modelPreferencesCollection.query(import_watermelondb9.Q.where("wallet_address", walletAddress)).fetch();
3643
3603
  if (results.length === 0) return false;
3644
3604
  await ctx.database.write(async () => {
3645
3605
  await results[0].destroyPermanently();
@@ -3744,37 +3704,6 @@ function useSettings(options) {
3744
3704
  };
3745
3705
  }
3746
3706
 
3747
- // src/lib/settingsStorage/schema.ts
3748
- var import_watermelondb8 = require("@nozbe/watermelondb");
3749
- var settingsStorageSchema = (0, import_watermelondb8.appSchema)({
3750
- version: 1,
3751
- tables: [
3752
- (0, import_watermelondb8.tableSchema)({
3753
- name: "modelPreferences",
3754
- columns: [
3755
- { name: "wallet_address", type: "string", isIndexed: true },
3756
- { name: "models", type: "string", isOptional: true }
3757
- // stored as JSON stringified ModelPreference[]
3758
- ]
3759
- })
3760
- ]
3761
- });
3762
-
3763
- // src/lib/settingsStorage/models.ts
3764
- var import_watermelondb9 = require("@nozbe/watermelondb");
3765
- var ModelPreference = class extends import_watermelondb9.Model {
3766
- /** User's wallet address */
3767
- get walletAddress() {
3768
- return this._getRaw("wallet_address");
3769
- }
3770
- /** Preferred model identifier */
3771
- get models() {
3772
- const value = this._getRaw("models");
3773
- return value ? value : void 0;
3774
- }
3775
- };
3776
- ModelPreference.table = "modelPreferences";
3777
-
3778
3707
  // src/react/usePdf.ts
3779
3708
  var import_react5 = require("react");
3780
3709
 
@@ -3845,13 +3774,13 @@ function usePdf() {
3845
3774
  const contexts = await Promise.all(
3846
3775
  pdfFiles.map(async (file) => {
3847
3776
  try {
3848
- const text = await extractTextFromPdf(file.url);
3849
- if (!text.trim()) {
3777
+ const text4 = await extractTextFromPdf(file.url);
3778
+ if (!text4.trim()) {
3850
3779
  console.warn(`No text found in PDF ${file.filename}`);
3851
3780
  return null;
3852
3781
  }
3853
3782
  return `[Context from PDF attachment ${file.filename}]:
3854
- ${text}`;
3783
+ ${text4}`;
3855
3784
  } catch (err) {
3856
3785
  console.error(`Failed to process PDF ${file.filename}:`, err);
3857
3786
  return null;
@@ -3931,15 +3860,15 @@ function useOCR() {
3931
3860
  const result = await import_tesseract.default.recognize(image, language);
3932
3861
  pageTexts.push(result.data.text);
3933
3862
  }
3934
- const text = pageTexts.join("\n\n");
3935
- if (!text.trim()) {
3863
+ const text4 = pageTexts.join("\n\n");
3864
+ if (!text4.trim()) {
3936
3865
  console.warn(
3937
3866
  `No text found in OCR source ${filename || "unknown"}`
3938
3867
  );
3939
3868
  return null;
3940
3869
  }
3941
3870
  return `[Context from OCR attachment ${filename || "unknown"}]:
3942
- ${text}`;
3871
+ ${text4}`;
3943
3872
  } catch (err) {
3944
3873
  console.error(
3945
3874
  `Failed to process OCR for ${file.filename || "unknown"}:`,
@@ -4292,15 +4221,934 @@ var extractConversationContext = (messages, maxMessages = 3) => {
4292
4221
  const userMessages = messages.filter((msg) => msg.role === "user").slice(-maxMessages).map((msg) => msg.content).join(" ");
4293
4222
  return userMessages.trim();
4294
4223
  };
4224
+
4225
+ // src/react/useDropboxBackup.ts
4226
+ var import_react11 = require("react");
4227
+
4228
+ // src/lib/backup/dropbox/api.ts
4229
+ var DROPBOX_API_URL = "https://api.dropboxapi.com/2";
4230
+ var DROPBOX_CONTENT_URL = "https://content.dropboxapi.com/2";
4231
+ var DEFAULT_BACKUP_FOLDER = "/ai-chat-app/conversations";
4232
+ async function ensureBackupFolder(accessToken, folder = DEFAULT_BACKUP_FOLDER) {
4233
+ try {
4234
+ await fetch(`${DROPBOX_API_URL}/files/create_folder_v2`, {
4235
+ method: "POST",
4236
+ headers: {
4237
+ Authorization: `Bearer ${accessToken}`,
4238
+ "Content-Type": "application/json"
4239
+ },
4240
+ body: JSON.stringify({
4241
+ path: folder,
4242
+ autorename: false
4243
+ })
4244
+ });
4245
+ } catch {
4246
+ }
4247
+ }
4248
+ async function uploadFileToDropbox(accessToken, filename, content, folder = DEFAULT_BACKUP_FOLDER) {
4249
+ await ensureBackupFolder(accessToken, folder);
4250
+ const path = `${folder}/${filename}`;
4251
+ const response = await fetch(`${DROPBOX_CONTENT_URL}/files/upload`, {
4252
+ method: "POST",
4253
+ headers: {
4254
+ Authorization: `Bearer ${accessToken}`,
4255
+ "Content-Type": "application/octet-stream",
4256
+ "Dropbox-API-Arg": JSON.stringify({
4257
+ path,
4258
+ mode: "overwrite",
4259
+ autorename: false,
4260
+ mute: true
4261
+ })
4262
+ },
4263
+ body: content
4264
+ });
4265
+ if (!response.ok) {
4266
+ const errorText = await response.text();
4267
+ throw new Error(`Dropbox upload failed: ${response.status} - ${errorText}`);
4268
+ }
4269
+ return response.json();
4270
+ }
4271
+ async function listDropboxFiles(accessToken, folder = DEFAULT_BACKUP_FOLDER) {
4272
+ await ensureBackupFolder(accessToken, folder);
4273
+ const response = await fetch(`${DROPBOX_API_URL}/files/list_folder`, {
4274
+ method: "POST",
4275
+ headers: {
4276
+ Authorization: `Bearer ${accessToken}`,
4277
+ "Content-Type": "application/json"
4278
+ },
4279
+ body: JSON.stringify({
4280
+ path: folder,
4281
+ recursive: false,
4282
+ include_deleted: false
4283
+ })
4284
+ });
4285
+ if (!response.ok) {
4286
+ const error = await response.json();
4287
+ if (error.error?.path?.[".tag"] === "not_found") {
4288
+ return [];
4289
+ }
4290
+ throw new Error(`Dropbox list failed: ${error.error_summary}`);
4291
+ }
4292
+ let data = await response.json();
4293
+ const allEntries = [...data.entries];
4294
+ while (data.has_more) {
4295
+ const continueResponse = await fetch(`${DROPBOX_API_URL}/files/list_folder/continue`, {
4296
+ method: "POST",
4297
+ headers: {
4298
+ Authorization: `Bearer ${accessToken}`,
4299
+ "Content-Type": "application/json"
4300
+ },
4301
+ body: JSON.stringify({
4302
+ cursor: data.cursor
4303
+ })
4304
+ });
4305
+ if (!continueResponse.ok) {
4306
+ const errorText = await continueResponse.text();
4307
+ throw new Error(`Dropbox list continue failed: ${continueResponse.status} - ${errorText}`);
4308
+ }
4309
+ data = await continueResponse.json();
4310
+ allEntries.push(...data.entries);
4311
+ }
4312
+ const files = allEntries.filter((entry) => entry[".tag"] === "file").map((entry) => ({
4313
+ id: entry.id,
4314
+ name: entry.name,
4315
+ path_lower: entry.path_lower,
4316
+ path_display: entry.path_display,
4317
+ client_modified: entry.client_modified || "",
4318
+ server_modified: entry.server_modified || "",
4319
+ size: entry.size || 0
4320
+ }));
4321
+ return files;
4322
+ }
4323
+ async function downloadDropboxFile(accessToken, path) {
4324
+ const response = await fetch(`${DROPBOX_CONTENT_URL}/files/download`, {
4325
+ method: "POST",
4326
+ headers: {
4327
+ Authorization: `Bearer ${accessToken}`,
4328
+ "Dropbox-API-Arg": JSON.stringify({ path })
4329
+ }
4330
+ });
4331
+ if (!response.ok) {
4332
+ throw new Error(`Dropbox download failed: ${response.status}`);
4333
+ }
4334
+ return response.blob();
4335
+ }
4336
+ async function findDropboxFile(accessToken, filename, folder = DEFAULT_BACKUP_FOLDER) {
4337
+ const files = await listDropboxFiles(accessToken, folder);
4338
+ return files.find((f) => f.name === filename) || null;
4339
+ }
4340
+
4341
+ // src/lib/backup/dropbox/backup.ts
4342
+ var isAuthError = (err) => err instanceof Error && (err.message.includes("401") || err.message.includes("invalid_access_token"));
4343
+ async function pushConversationToDropbox(database, conversationId, userAddress, token, deps, backupFolder = DEFAULT_BACKUP_FOLDER, _retried = false) {
4344
+ try {
4345
+ await deps.requestEncryptionKey(userAddress);
4346
+ const filename = `${conversationId}.json`;
4347
+ const existingFile = await findDropboxFile(token, filename, backupFolder);
4348
+ if (existingFile) {
4349
+ const { Q: Q4 } = await import("@nozbe/watermelondb");
4350
+ const conversationsCollection = database.get("conversations");
4351
+ const records = await conversationsCollection.query(Q4.where("conversation_id", conversationId)).fetch();
4352
+ if (records.length > 0) {
4353
+ const conversation = conversationToStored(records[0]);
4354
+ const localUpdated = conversation.updatedAt.getTime();
4355
+ const remoteModified = new Date(existingFile.server_modified).getTime();
4356
+ if (localUpdated <= remoteModified) {
4357
+ return "skipped";
4358
+ }
4359
+ }
4360
+ }
4361
+ const exportResult = await deps.exportConversation(
4362
+ conversationId,
4363
+ userAddress
4364
+ );
4365
+ if (!exportResult.success || !exportResult.blob) {
4366
+ return "failed";
4367
+ }
4368
+ await uploadFileToDropbox(token, filename, exportResult.blob, backupFolder);
4369
+ return "uploaded";
4370
+ } catch (err) {
4371
+ if (isAuthError(err) && !_retried) {
4372
+ try {
4373
+ const newToken = await deps.requestDropboxAccess();
4374
+ return pushConversationToDropbox(
4375
+ database,
4376
+ conversationId,
4377
+ userAddress,
4378
+ newToken,
4379
+ deps,
4380
+ backupFolder,
4381
+ true
4382
+ );
4383
+ } catch {
4384
+ return "failed";
4385
+ }
4386
+ }
4387
+ return "failed";
4388
+ }
4389
+ }
4390
+ async function performDropboxExport(database, userAddress, token, deps, onProgress, backupFolder = DEFAULT_BACKUP_FOLDER) {
4391
+ await deps.requestEncryptionKey(userAddress);
4392
+ const { Q: Q4 } = await import("@nozbe/watermelondb");
4393
+ const conversationsCollection = database.get("conversations");
4394
+ const records = await conversationsCollection.query(Q4.where("is_deleted", false)).fetch();
4395
+ const conversations = records.map(conversationToStored);
4396
+ const total = conversations.length;
4397
+ if (total === 0) {
4398
+ return { success: true, uploaded: 0, skipped: 0, total: 0 };
4399
+ }
4400
+ let uploaded = 0;
4401
+ let skipped = 0;
4402
+ for (let i = 0; i < conversations.length; i++) {
4403
+ const conv = conversations[i];
4404
+ onProgress?.(i + 1, total);
4405
+ const result = await pushConversationToDropbox(
4406
+ database,
4407
+ conv.conversationId,
4408
+ userAddress,
4409
+ token,
4410
+ deps,
4411
+ backupFolder
4412
+ );
4413
+ if (result === "uploaded") uploaded++;
4414
+ if (result === "skipped") skipped++;
4415
+ }
4416
+ return { success: true, uploaded, skipped, total };
4417
+ }
4418
+ async function performDropboxImport(userAddress, token, deps, onProgress, backupFolder = DEFAULT_BACKUP_FOLDER) {
4419
+ await deps.requestEncryptionKey(userAddress);
4420
+ const remoteFiles = await listDropboxFiles(token, backupFolder);
4421
+ if (remoteFiles.length === 0) {
4422
+ return {
4423
+ success: false,
4424
+ restored: 0,
4425
+ failed: 0,
4426
+ total: 0,
4427
+ noBackupsFound: true
4428
+ };
4429
+ }
4430
+ const jsonFiles = remoteFiles.filter(
4431
+ (file) => file.name.endsWith(".json")
4432
+ );
4433
+ const total = jsonFiles.length;
4434
+ let restored = 0;
4435
+ let failed = 0;
4436
+ let currentToken = token;
4437
+ for (let i = 0; i < jsonFiles.length; i++) {
4438
+ const file = jsonFiles[i];
4439
+ onProgress?.(i + 1, total);
4440
+ try {
4441
+ const blob = await downloadDropboxFile(currentToken, file.path_lower);
4442
+ const result = await deps.importConversation(blob, userAddress);
4443
+ if (result.success) {
4444
+ restored++;
4445
+ } else {
4446
+ failed++;
4447
+ }
4448
+ } catch (err) {
4449
+ if (isAuthError(err)) {
4450
+ try {
4451
+ currentToken = await deps.requestDropboxAccess();
4452
+ const blob = await downloadDropboxFile(currentToken, file.path_lower);
4453
+ const result = await deps.importConversation(blob, userAddress);
4454
+ if (result.success) {
4455
+ restored++;
4456
+ } else {
4457
+ failed++;
4458
+ }
4459
+ } catch {
4460
+ failed++;
4461
+ }
4462
+ } else {
4463
+ failed++;
4464
+ }
4465
+ }
4466
+ }
4467
+ return { success: true, restored, failed, total };
4468
+ }
4469
+
4470
+ // src/react/useDropboxAuth.ts
4471
+ var import_react10 = require("react");
4472
+
4473
+ // src/lib/backup/dropbox/auth.ts
4474
+ var DROPBOX_AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
4475
+ var DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token";
4476
+ var TOKEN_STORAGE_KEY = "dropbox_access_token";
4477
+ var VERIFIER_STORAGE_KEY = "dropbox_code_verifier";
4478
+ function generateCodeVerifier() {
4479
+ const array = new Uint8Array(32);
4480
+ crypto.getRandomValues(array);
4481
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
4482
+ }
4483
+ async function generateCodeChallenge(verifier) {
4484
+ const encoder = new TextEncoder();
4485
+ const data = encoder.encode(verifier);
4486
+ const hash = await crypto.subtle.digest("SHA-256", data);
4487
+ const base64 = btoa(String.fromCharCode(...new Uint8Array(hash)));
4488
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
4489
+ }
4490
+ function getStoredToken() {
4491
+ if (typeof window === "undefined") return null;
4492
+ return sessionStorage.getItem(TOKEN_STORAGE_KEY);
4493
+ }
4494
+ function storeToken(token) {
4495
+ if (typeof window === "undefined") return;
4496
+ sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
4497
+ }
4498
+ function clearToken() {
4499
+ if (typeof window === "undefined") return;
4500
+ sessionStorage.removeItem(TOKEN_STORAGE_KEY);
4501
+ }
4502
+ function getStoredVerifier() {
4503
+ if (typeof window === "undefined") return null;
4504
+ return sessionStorage.getItem(VERIFIER_STORAGE_KEY);
4505
+ }
4506
+ function storeVerifier(verifier) {
4507
+ if (typeof window === "undefined") return;
4508
+ sessionStorage.setItem(VERIFIER_STORAGE_KEY, verifier);
4509
+ }
4510
+ function clearVerifier() {
4511
+ if (typeof window === "undefined") return;
4512
+ sessionStorage.removeItem(VERIFIER_STORAGE_KEY);
4513
+ }
4514
+ function getRedirectUri(callbackPath) {
4515
+ if (typeof window === "undefined") return "";
4516
+ return `${window.location.origin}${callbackPath}`;
4517
+ }
4518
+ async function handleDropboxCallback(appKey, callbackPath) {
4519
+ if (typeof window === "undefined") return null;
4520
+ const url = new URL(window.location.href);
4521
+ const code = url.searchParams.get("code");
4522
+ const state = url.searchParams.get("state");
4523
+ if (!code || state !== "dropbox_auth") return null;
4524
+ const verifier = getStoredVerifier();
4525
+ if (!verifier) return null;
4526
+ try {
4527
+ const response = await fetch(DROPBOX_TOKEN_URL, {
4528
+ method: "POST",
4529
+ headers: {
4530
+ "Content-Type": "application/x-www-form-urlencoded"
4531
+ },
4532
+ body: new URLSearchParams({
4533
+ code,
4534
+ grant_type: "authorization_code",
4535
+ client_id: appKey,
4536
+ redirect_uri: getRedirectUri(callbackPath),
4537
+ code_verifier: verifier
4538
+ })
4539
+ });
4540
+ if (!response.ok) {
4541
+ throw new Error("Token exchange failed");
4542
+ }
4543
+ const data = await response.json();
4544
+ const token = data.access_token;
4545
+ if (typeof token !== "string" || token.trim() === "") {
4546
+ throw new Error("Invalid token response: access_token is missing or empty");
4547
+ }
4548
+ storeToken(token);
4549
+ clearVerifier();
4550
+ window.history.replaceState({}, "", window.location.pathname);
4551
+ return token;
4552
+ } catch {
4553
+ clearVerifier();
4554
+ return null;
4555
+ }
4556
+ }
4557
+ async function startDropboxAuth(appKey, callbackPath) {
4558
+ const verifier = generateCodeVerifier();
4559
+ const challenge = await generateCodeChallenge(verifier);
4560
+ storeVerifier(verifier);
4561
+ const params = new URLSearchParams({
4562
+ client_id: appKey,
4563
+ redirect_uri: getRedirectUri(callbackPath),
4564
+ response_type: "code",
4565
+ code_challenge: challenge,
4566
+ code_challenge_method: "S256",
4567
+ state: "dropbox_auth",
4568
+ token_access_type: "offline"
4569
+ });
4570
+ window.location.href = `${DROPBOX_AUTH_URL}?${params.toString()}`;
4571
+ return new Promise(() => {
4572
+ });
4573
+ }
4574
+ async function requestDropboxAccess(appKey, callbackPath) {
4575
+ if (!appKey) {
4576
+ throw new Error("Dropbox is not configured");
4577
+ }
4578
+ const storedToken = getStoredToken();
4579
+ if (storedToken) {
4580
+ return storedToken;
4581
+ }
4582
+ return startDropboxAuth(appKey, callbackPath);
4583
+ }
4584
+
4585
+ // src/react/useDropboxAuth.ts
4586
+ var DropboxAuthContext = (0, import_react10.createContext)(null);
4587
+ function DropboxAuthProvider({
4588
+ appKey,
4589
+ callbackPath = "/auth/dropbox/callback",
4590
+ children
4591
+ }) {
4592
+ const [accessToken, setAccessToken] = (0, import_react10.useState)(null);
4593
+ const isConfigured = !!appKey;
4594
+ (0, import_react10.useEffect)(() => {
4595
+ const storedToken = getStoredToken();
4596
+ if (storedToken) {
4597
+ setAccessToken(storedToken);
4598
+ }
4599
+ }, []);
4600
+ (0, import_react10.useEffect)(() => {
4601
+ if (!isConfigured || !appKey) return;
4602
+ const handleCallback = async () => {
4603
+ const token = await handleDropboxCallback(appKey, callbackPath);
4604
+ if (token) {
4605
+ setAccessToken(token);
4606
+ }
4607
+ };
4608
+ handleCallback();
4609
+ }, [appKey, callbackPath, isConfigured]);
4610
+ const requestAccess = (0, import_react10.useCallback)(async () => {
4611
+ if (!isConfigured || !appKey) {
4612
+ throw new Error("Dropbox is not configured");
4613
+ }
4614
+ if (accessToken) {
4615
+ return accessToken;
4616
+ }
4617
+ const storedToken = getStoredToken();
4618
+ if (storedToken) {
4619
+ setAccessToken(storedToken);
4620
+ return storedToken;
4621
+ }
4622
+ return requestDropboxAccess(appKey, callbackPath);
4623
+ }, [accessToken, appKey, callbackPath, isConfigured]);
4624
+ const logout = (0, import_react10.useCallback)(() => {
4625
+ clearToken();
4626
+ setAccessToken(null);
4627
+ }, []);
4628
+ return (0, import_react10.createElement)(
4629
+ DropboxAuthContext.Provider,
4630
+ {
4631
+ value: {
4632
+ accessToken,
4633
+ isAuthenticated: !!accessToken,
4634
+ isConfigured,
4635
+ requestAccess,
4636
+ logout
4637
+ }
4638
+ },
4639
+ children
4640
+ );
4641
+ }
4642
+ function useDropboxAuth() {
4643
+ const context = (0, import_react10.useContext)(DropboxAuthContext);
4644
+ if (!context) {
4645
+ throw new Error("useDropboxAuth must be used within DropboxAuthProvider");
4646
+ }
4647
+ return context;
4648
+ }
4649
+
4650
+ // src/react/useDropboxBackup.ts
4651
+ function useDropboxBackup(options) {
4652
+ const {
4653
+ database,
4654
+ userAddress,
4655
+ requestEncryptionKey: requestEncryptionKey2,
4656
+ exportConversation,
4657
+ importConversation,
4658
+ backupFolder = DEFAULT_BACKUP_FOLDER
4659
+ } = options;
4660
+ const {
4661
+ accessToken: dropboxToken,
4662
+ isConfigured: isDropboxConfigured,
4663
+ requestAccess: requestDropboxAccess2
4664
+ } = useDropboxAuth();
4665
+ const deps = (0, import_react11.useMemo)(
4666
+ () => ({
4667
+ requestDropboxAccess: requestDropboxAccess2,
4668
+ requestEncryptionKey: requestEncryptionKey2,
4669
+ exportConversation,
4670
+ importConversation
4671
+ }),
4672
+ [requestDropboxAccess2, requestEncryptionKey2, exportConversation, importConversation]
4673
+ );
4674
+ const ensureToken = (0, import_react11.useCallback)(async () => {
4675
+ if (dropboxToken) return dropboxToken;
4676
+ try {
4677
+ return await requestDropboxAccess2();
4678
+ } catch {
4679
+ return null;
4680
+ }
4681
+ }, [dropboxToken, requestDropboxAccess2]);
4682
+ const backup = (0, import_react11.useCallback)(
4683
+ async (backupOptions) => {
4684
+ if (!userAddress) {
4685
+ return { error: "Please sign in to backup to Dropbox" };
4686
+ }
4687
+ const token = await ensureToken();
4688
+ if (!token) {
4689
+ return { error: "Dropbox access denied" };
4690
+ }
4691
+ try {
4692
+ return await performDropboxExport(
4693
+ database,
4694
+ userAddress,
4695
+ token,
4696
+ deps,
4697
+ backupOptions?.onProgress,
4698
+ backupFolder
4699
+ );
4700
+ } catch (err) {
4701
+ return {
4702
+ error: err instanceof Error ? err.message : "Failed to backup to Dropbox"
4703
+ };
4704
+ }
4705
+ },
4706
+ [database, userAddress, ensureToken, deps, backupFolder]
4707
+ );
4708
+ const restore = (0, import_react11.useCallback)(
4709
+ async (restoreOptions) => {
4710
+ if (!userAddress) {
4711
+ return { error: "Please sign in to restore from Dropbox" };
4712
+ }
4713
+ const token = await ensureToken();
4714
+ if (!token) {
4715
+ return { error: "Dropbox access denied" };
4716
+ }
4717
+ try {
4718
+ return await performDropboxImport(
4719
+ userAddress,
4720
+ token,
4721
+ deps,
4722
+ restoreOptions?.onProgress,
4723
+ backupFolder
4724
+ );
4725
+ } catch (err) {
4726
+ return {
4727
+ error: err instanceof Error ? err.message : "Failed to restore from Dropbox"
4728
+ };
4729
+ }
4730
+ },
4731
+ [userAddress, ensureToken, deps, backupFolder]
4732
+ );
4733
+ return {
4734
+ backup,
4735
+ restore,
4736
+ isConfigured: isDropboxConfigured,
4737
+ isAuthenticated: !!dropboxToken
4738
+ };
4739
+ }
4740
+
4741
+ // src/react/useGoogleDriveBackup.ts
4742
+ var import_react12 = require("react");
4743
+
4744
+ // src/lib/backup/google/api.ts
4745
+ var DRIVE_API_URL = "https://www.googleapis.com/drive/v3";
4746
+ var DRIVE_UPLOAD_URL = "https://www.googleapis.com/upload/drive/v3";
4747
+ var FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
4748
+ function escapeQueryValue(value) {
4749
+ return value.replace(/'/g, "''");
4750
+ }
4751
+ var DEFAULT_ROOT_FOLDER = "ai-chat-app";
4752
+ var DEFAULT_CONVERSATIONS_FOLDER = "conversations";
4753
+ async function ensureFolder(accessToken, name, parentId) {
4754
+ const parentQuery = parentId ? `'${escapeQueryValue(parentId)}' in parents and ` : "";
4755
+ const query = `${parentQuery}mimeType='${FOLDER_MIME_TYPE}' and name='${escapeQueryValue(name)}' and trashed=false`;
4756
+ const response = await fetch(
4757
+ `${DRIVE_API_URL}/files?q=${encodeURIComponent(query)}&fields=files(id)`,
4758
+ {
4759
+ headers: { Authorization: `Bearer ${accessToken}` }
4760
+ }
4761
+ );
4762
+ if (!response.ok) {
4763
+ throw new Error(`Failed to search for folder ${name}: ${response.status}`);
4764
+ }
4765
+ const data = await response.json();
4766
+ if (data.files && data.files.length > 0) {
4767
+ return data.files[0].id;
4768
+ }
4769
+ const body = {
4770
+ name,
4771
+ mimeType: FOLDER_MIME_TYPE
4772
+ };
4773
+ if (parentId) {
4774
+ body.parents = [parentId];
4775
+ }
4776
+ const createResponse = await fetch(`${DRIVE_API_URL}/files`, {
4777
+ method: "POST",
4778
+ headers: {
4779
+ Authorization: `Bearer ${accessToken}`,
4780
+ "Content-Type": "application/json"
4781
+ },
4782
+ body: JSON.stringify(body)
4783
+ });
4784
+ if (!createResponse.ok) {
4785
+ throw new Error(`Failed to create folder ${name}: ${createResponse.status}`);
4786
+ }
4787
+ const folderData = await createResponse.json();
4788
+ return folderData.id;
4789
+ }
4790
+ async function getBackupFolder(accessToken, rootFolder = DEFAULT_ROOT_FOLDER, subfolder = DEFAULT_CONVERSATIONS_FOLDER) {
4791
+ const rootId = await ensureFolder(accessToken, rootFolder);
4792
+ return ensureFolder(accessToken, subfolder, rootId);
4793
+ }
4794
+ async function uploadFileToDrive(accessToken, folderId, content, filename) {
4795
+ const metadata = {
4796
+ name: filename,
4797
+ parents: [folderId],
4798
+ mimeType: "application/json"
4799
+ };
4800
+ const form = new FormData();
4801
+ form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }));
4802
+ form.append("file", content);
4803
+ const response = await fetch(`${DRIVE_UPLOAD_URL}/files?uploadType=multipart`, {
4804
+ method: "POST",
4805
+ headers: { Authorization: `Bearer ${accessToken}` },
4806
+ body: form
4807
+ });
4808
+ if (!response.ok) {
4809
+ const errorText = await response.text();
4810
+ throw new Error(`Drive upload failed: ${response.status} - ${errorText}`);
4811
+ }
4812
+ return response.json();
4813
+ }
4814
+ async function updateDriveFile(accessToken, fileId, content) {
4815
+ const response = await fetch(`${DRIVE_UPLOAD_URL}/files/${fileId}?uploadType=media`, {
4816
+ method: "PATCH",
4817
+ headers: {
4818
+ Authorization: `Bearer ${accessToken}`,
4819
+ "Content-Type": "application/json"
4820
+ },
4821
+ body: content
4822
+ });
4823
+ if (!response.ok) {
4824
+ const errorText = await response.text();
4825
+ throw new Error(`Drive update failed: ${response.status} - ${errorText}`);
4826
+ }
4827
+ return response.json();
4828
+ }
4829
+ async function listDriveFiles(accessToken, folderId) {
4830
+ const query = `'${escapeQueryValue(folderId)}' in parents and mimeType='application/json' and trashed=false`;
4831
+ const fields = "files(id,name,createdTime,modifiedTime,size)";
4832
+ const response = await fetch(
4833
+ `${DRIVE_API_URL}/files?q=${encodeURIComponent(query)}&fields=${fields}&pageSize=1000`,
4834
+ {
4835
+ headers: { Authorization: `Bearer ${accessToken}` }
4836
+ }
4837
+ );
4838
+ if (!response.ok) {
4839
+ throw new Error(`Failed to list files: ${response.status}`);
4840
+ }
4841
+ const data = await response.json();
4842
+ return data.files ?? [];
4843
+ }
4844
+ async function downloadDriveFile(accessToken, fileId) {
4845
+ const response = await fetch(`${DRIVE_API_URL}/files/${fileId}?alt=media`, {
4846
+ headers: { Authorization: `Bearer ${accessToken}` }
4847
+ });
4848
+ if (!response.ok) {
4849
+ throw new Error(`Failed to download file: ${response.status}`);
4850
+ }
4851
+ return response.blob();
4852
+ }
4853
+ async function findDriveFile(accessToken, folderId, filename) {
4854
+ const query = `'${escapeQueryValue(folderId)}' in parents and name='${escapeQueryValue(filename)}' and trashed=false`;
4855
+ const fields = "files(id,name,createdTime,modifiedTime,size)";
4856
+ const response = await fetch(
4857
+ `${DRIVE_API_URL}/files?q=${encodeURIComponent(query)}&fields=${fields}&pageSize=1`,
4858
+ {
4859
+ headers: { Authorization: `Bearer ${accessToken}` }
4860
+ }
4861
+ );
4862
+ if (!response.ok) {
4863
+ throw new Error(`Failed to find file: ${response.status}`);
4864
+ }
4865
+ const data = await response.json();
4866
+ return data.files?.[0] ?? null;
4867
+ }
4868
+
4869
+ // src/lib/backup/google/backup.ts
4870
+ var isAuthError2 = (err) => err instanceof Error && (err.message.includes("401") || err.message.includes("403"));
4871
+ async function getConversationsFolder(token, requestDriveAccess, rootFolder, subfolder) {
4872
+ try {
4873
+ const folderId = await getBackupFolder(token, rootFolder, subfolder);
4874
+ return { folderId, token };
4875
+ } catch (err) {
4876
+ if (isAuthError2(err)) {
4877
+ try {
4878
+ const newToken = await requestDriveAccess();
4879
+ const folderId = await getBackupFolder(newToken, rootFolder, subfolder);
4880
+ return { folderId, token: newToken };
4881
+ } catch {
4882
+ return null;
4883
+ }
4884
+ }
4885
+ throw err;
4886
+ }
4887
+ }
4888
+ async function pushConversationToDrive(database, conversationId, userAddress, token, deps, rootFolder = DEFAULT_ROOT_FOLDER, subfolder = DEFAULT_CONVERSATIONS_FOLDER, _retried = false) {
4889
+ try {
4890
+ await deps.requestEncryptionKey(userAddress);
4891
+ const folderResult = await getConversationsFolder(
4892
+ token,
4893
+ deps.requestDriveAccess,
4894
+ rootFolder,
4895
+ subfolder
4896
+ );
4897
+ if (!folderResult) return "failed";
4898
+ const { folderId, token: activeToken } = folderResult;
4899
+ const filename = `${conversationId}.json`;
4900
+ const existingFile = await findDriveFile(activeToken, folderId, filename);
4901
+ if (existingFile) {
4902
+ const { Q: Q4 } = await import("@nozbe/watermelondb");
4903
+ const conversationsCollection = database.get("conversations");
4904
+ const records = await conversationsCollection.query(Q4.where("conversation_id", conversationId)).fetch();
4905
+ if (records.length > 0) {
4906
+ const conversation = conversationToStored(records[0]);
4907
+ const localUpdated = conversation.updatedAt.getTime();
4908
+ const remoteModified = new Date(existingFile.modifiedTime).getTime();
4909
+ if (localUpdated <= remoteModified) {
4910
+ return "skipped";
4911
+ }
4912
+ }
4913
+ }
4914
+ const exportResult = await deps.exportConversation(
4915
+ conversationId,
4916
+ userAddress
4917
+ );
4918
+ if (!exportResult.success || !exportResult.blob) {
4919
+ return "failed";
4920
+ }
4921
+ if (existingFile) {
4922
+ await updateDriveFile(activeToken, existingFile.id, exportResult.blob);
4923
+ } else {
4924
+ await uploadFileToDrive(
4925
+ activeToken,
4926
+ folderId,
4927
+ exportResult.blob,
4928
+ filename
4929
+ );
4930
+ }
4931
+ return "uploaded";
4932
+ } catch (err) {
4933
+ if (isAuthError2(err) && !_retried) {
4934
+ try {
4935
+ const newToken = await deps.requestDriveAccess();
4936
+ return pushConversationToDrive(
4937
+ database,
4938
+ conversationId,
4939
+ userAddress,
4940
+ newToken,
4941
+ deps,
4942
+ rootFolder,
4943
+ subfolder,
4944
+ true
4945
+ );
4946
+ } catch {
4947
+ return "failed";
4948
+ }
4949
+ }
4950
+ return "failed";
4951
+ }
4952
+ }
4953
+ async function performGoogleDriveExport(database, userAddress, token, deps, onProgress, rootFolder = DEFAULT_ROOT_FOLDER, subfolder = DEFAULT_CONVERSATIONS_FOLDER) {
4954
+ await deps.requestEncryptionKey(userAddress);
4955
+ const folderResult = await getConversationsFolder(
4956
+ token,
4957
+ deps.requestDriveAccess,
4958
+ rootFolder,
4959
+ subfolder
4960
+ );
4961
+ if (!folderResult) {
4962
+ return { success: false, uploaded: 0, skipped: 0, total: 0 };
4963
+ }
4964
+ const { token: activeToken } = folderResult;
4965
+ const { Q: Q4 } = await import("@nozbe/watermelondb");
4966
+ const conversationsCollection = database.get("conversations");
4967
+ const records = await conversationsCollection.query(Q4.where("is_deleted", false)).fetch();
4968
+ const conversations = records.map(conversationToStored);
4969
+ const total = conversations.length;
4970
+ if (total === 0) {
4971
+ return { success: true, uploaded: 0, skipped: 0, total: 0 };
4972
+ }
4973
+ let uploaded = 0;
4974
+ let skipped = 0;
4975
+ for (let i = 0; i < conversations.length; i++) {
4976
+ const conv = conversations[i];
4977
+ onProgress?.(i + 1, total);
4978
+ const result = await pushConversationToDrive(
4979
+ database,
4980
+ conv.conversationId,
4981
+ userAddress,
4982
+ activeToken,
4983
+ deps,
4984
+ rootFolder,
4985
+ subfolder
4986
+ );
4987
+ if (result === "uploaded") uploaded++;
4988
+ if (result === "skipped") skipped++;
4989
+ }
4990
+ return { success: true, uploaded, skipped, total };
4991
+ }
4992
+ async function performGoogleDriveImport(userAddress, token, deps, onProgress, rootFolder = DEFAULT_ROOT_FOLDER, subfolder = DEFAULT_CONVERSATIONS_FOLDER) {
4993
+ await deps.requestEncryptionKey(userAddress);
4994
+ const folderResult = await getConversationsFolder(
4995
+ token,
4996
+ deps.requestDriveAccess,
4997
+ rootFolder,
4998
+ subfolder
4999
+ );
5000
+ if (!folderResult) {
5001
+ return {
5002
+ success: false,
5003
+ restored: 0,
5004
+ failed: 0,
5005
+ total: 0,
5006
+ noBackupsFound: true
5007
+ };
5008
+ }
5009
+ const { folderId, token: activeToken } = folderResult;
5010
+ const remoteFiles = await listDriveFiles(activeToken, folderId);
5011
+ if (remoteFiles.length === 0) {
5012
+ return {
5013
+ success: false,
5014
+ restored: 0,
5015
+ failed: 0,
5016
+ total: 0,
5017
+ noBackupsFound: true
5018
+ };
5019
+ }
5020
+ const jsonFiles = remoteFiles.filter(
5021
+ (file) => file.name.endsWith(".json")
5022
+ );
5023
+ const total = jsonFiles.length;
5024
+ let restored = 0;
5025
+ let failed = 0;
5026
+ for (let i = 0; i < jsonFiles.length; i++) {
5027
+ const file = jsonFiles[i];
5028
+ onProgress?.(i + 1, total);
5029
+ try {
5030
+ const blob = await downloadDriveFile(activeToken, file.id);
5031
+ const result = await deps.importConversation(blob, userAddress);
5032
+ if (result.success) {
5033
+ restored++;
5034
+ } else {
5035
+ failed++;
5036
+ }
5037
+ } catch {
5038
+ failed++;
5039
+ }
5040
+ }
5041
+ return { success: true, restored, failed, total };
5042
+ }
5043
+
5044
+ // src/react/useGoogleDriveBackup.ts
5045
+ function useGoogleDriveBackup(options) {
5046
+ const {
5047
+ database,
5048
+ userAddress,
5049
+ accessToken,
5050
+ requestDriveAccess,
5051
+ requestEncryptionKey: requestEncryptionKey2,
5052
+ exportConversation,
5053
+ importConversation,
5054
+ rootFolder = DEFAULT_ROOT_FOLDER,
5055
+ conversationsFolder = DEFAULT_CONVERSATIONS_FOLDER
5056
+ } = options;
5057
+ const deps = (0, import_react12.useMemo)(
5058
+ () => ({
5059
+ requestDriveAccess,
5060
+ requestEncryptionKey: requestEncryptionKey2,
5061
+ exportConversation,
5062
+ importConversation
5063
+ }),
5064
+ [
5065
+ requestDriveAccess,
5066
+ requestEncryptionKey2,
5067
+ exportConversation,
5068
+ importConversation
5069
+ ]
5070
+ );
5071
+ const ensureToken = (0, import_react12.useCallback)(async () => {
5072
+ if (accessToken) return accessToken;
5073
+ try {
5074
+ return await requestDriveAccess();
5075
+ } catch {
5076
+ return null;
5077
+ }
5078
+ }, [accessToken, requestDriveAccess]);
5079
+ const backup = (0, import_react12.useCallback)(
5080
+ async (backupOptions) => {
5081
+ if (!userAddress) {
5082
+ return { error: "Please sign in to backup to Google Drive" };
5083
+ }
5084
+ const token = await ensureToken();
5085
+ if (!token) {
5086
+ return { error: "Google Drive access denied" };
5087
+ }
5088
+ try {
5089
+ return await performGoogleDriveExport(
5090
+ database,
5091
+ userAddress,
5092
+ token,
5093
+ deps,
5094
+ backupOptions?.onProgress,
5095
+ rootFolder,
5096
+ conversationsFolder
5097
+ );
5098
+ } catch (err) {
5099
+ return {
5100
+ error: err instanceof Error ? err.message : "Failed to backup to Google Drive"
5101
+ };
5102
+ }
5103
+ },
5104
+ [database, userAddress, ensureToken, deps, rootFolder, conversationsFolder]
5105
+ );
5106
+ const restore = (0, import_react12.useCallback)(
5107
+ async (restoreOptions) => {
5108
+ if (!userAddress) {
5109
+ return { error: "Please sign in to restore from Google Drive" };
5110
+ }
5111
+ const token = await ensureToken();
5112
+ if (!token) {
5113
+ return { error: "Google Drive access denied" };
5114
+ }
5115
+ try {
5116
+ return await performGoogleDriveImport(
5117
+ userAddress,
5118
+ token,
5119
+ deps,
5120
+ restoreOptions?.onProgress,
5121
+ rootFolder,
5122
+ conversationsFolder
5123
+ );
5124
+ } catch (err) {
5125
+ return {
5126
+ error: err instanceof Error ? err.message : "Failed to restore from Google Drive"
5127
+ };
5128
+ }
5129
+ },
5130
+ [userAddress, ensureToken, deps, rootFolder, conversationsFolder]
5131
+ );
5132
+ return {
5133
+ backup,
5134
+ restore,
5135
+ isAuthenticated: !!accessToken
5136
+ };
5137
+ }
4295
5138
  // Annotate the CommonJS export names for ESM import in node:
4296
5139
  0 && (module.exports = {
4297
5140
  ChatConversation,
4298
5141
  ChatMessage,
5142
+ DEFAULT_BACKUP_FOLDER,
5143
+ DEFAULT_DRIVE_CONVERSATIONS_FOLDER,
5144
+ DEFAULT_DRIVE_ROOT_FOLDER,
4299
5145
  DEFAULT_TOOL_SELECTOR_MODEL,
5146
+ DropboxAuthProvider,
4300
5147
  StoredMemoryModel,
4301
5148
  StoredModelPreferenceModel,
4302
5149
  chatStorageMigrations,
4303
5150
  chatStorageSchema,
5151
+ clearDropboxToken,
4304
5152
  createMemoryContextSystemMessage,
4305
5153
  decryptData,
4306
5154
  decryptDataBytes,
@@ -4311,14 +5159,19 @@ var extractConversationContext = (messages, maxMessages = 3) => {
4311
5159
  generateCompositeKey,
4312
5160
  generateConversationId,
4313
5161
  generateUniqueKey,
5162
+ getDropboxToken,
4314
5163
  hasEncryptionKey,
4315
5164
  memoryStorageSchema,
4316
5165
  requestEncryptionKey,
4317
5166
  selectTool,
4318
5167
  settingsStorageSchema,
5168
+ storeDropboxToken,
4319
5169
  useChat,
4320
5170
  useChatStorage,
5171
+ useDropboxAuth,
5172
+ useDropboxBackup,
4321
5173
  useEncryption,
5174
+ useGoogleDriveBackup,
4322
5175
  useImageGeneration,
4323
5176
  useMemoryStorage,
4324
5177
  useModels,