@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.
- package/dist/expo/index.cjs +240 -318
- package/dist/expo/index.d.mts +74 -432
- package/dist/expo/index.d.ts +74 -432
- package/dist/expo/index.mjs +225 -297
- package/dist/index.cjs +46 -2
- package/dist/index.d.mts +175 -2
- package/dist/index.d.ts +175 -2
- package/dist/index.mjs +41 -1
- package/dist/react/index.cjs +1203 -350
- package/dist/react/index.d.mts +423 -465
- package/dist/react/index.d.ts +423 -465
- package/dist/react/index.mjs +1179 -323
- package/package.json +2 -2
package/dist/react/index.cjs
CHANGED
|
@@ -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(
|
|
1079
|
+
on_finalized_text(text4) {
|
|
1062
1080
|
if (signal?.aborted) {
|
|
1063
1081
|
throw new Error("AbortError");
|
|
1064
1082
|
}
|
|
1065
|
-
this.cb(
|
|
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/
|
|
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/
|
|
1754
|
-
var
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 ? [
|
|
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/
|
|
2260
|
-
var
|
|
2261
|
-
var
|
|
2262
|
-
|
|
2263
|
-
|
|
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,
|
|
2266
|
-
name: "
|
|
2410
|
+
(0, import_watermelondb4.tableSchema)({
|
|
2411
|
+
name: "memories",
|
|
2267
2412
|
columns: [
|
|
2268
|
-
{ name: "
|
|
2269
|
-
|
|
2270
|
-
{ name: "
|
|
2271
|
-
{ name: "
|
|
2272
|
-
|
|
2273
|
-
{ name: "
|
|
2274
|
-
{ name: "
|
|
2275
|
-
{ name: "
|
|
2276
|
-
|
|
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: "
|
|
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/
|
|
2319
|
-
var
|
|
2320
|
-
var
|
|
2321
|
-
|
|
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/
|
|
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/
|
|
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(
|
|
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
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
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
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
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:
|
|
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(
|
|
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
|
|
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(
|
|
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/
|
|
3500
|
-
var
|
|
3501
|
-
|
|
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,
|
|
3505
|
-
name: "
|
|
3546
|
+
(0, import_watermelondb7.tableSchema)({
|
|
3547
|
+
name: "modelPreferences",
|
|
3506
3548
|
columns: [
|
|
3507
|
-
|
|
3508
|
-
{ name: "
|
|
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/
|
|
3538
|
-
var
|
|
3539
|
-
var
|
|
3540
|
-
|
|
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
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
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/
|
|
3610
|
-
var
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
3849
|
-
if (!
|
|
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
|
-
${
|
|
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
|
|
3935
|
-
if (!
|
|
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
|
-
${
|
|
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,
|