@reverbia/sdk 1.0.0-next.20260110155148 → 1.0.0-next.20260110214809
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 +4 -3
- package/dist/expo/index.d.mts +10 -3
- package/dist/expo/index.d.ts +10 -3
- package/dist/expo/index.mjs +4 -3
- package/dist/react/index.cjs +397 -22
- package/dist/react/index.d.mts +110 -4
- package/dist/react/index.d.ts +110 -4
- package/dist/react/index.mjs +423 -55
- package/package.json +1 -1
package/dist/expo/index.cjs
CHANGED
|
@@ -3191,17 +3191,18 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
|
|
|
3191
3191
|
if (existingKey) {
|
|
3192
3192
|
return;
|
|
3193
3193
|
}
|
|
3194
|
+
const signOptions = { showWalletUIs: false };
|
|
3194
3195
|
let signature;
|
|
3195
3196
|
try {
|
|
3196
3197
|
if (embeddedWalletSigner) {
|
|
3197
|
-
signature = await embeddedWalletSigner(SIGN_MESSAGE);
|
|
3198
|
+
signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
|
|
3198
3199
|
} else {
|
|
3199
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
3200
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
3200
3201
|
}
|
|
3201
3202
|
} catch (error) {
|
|
3202
3203
|
if (embeddedWalletSigner && error instanceof Error) {
|
|
3203
3204
|
console.warn("Embedded wallet signing failed, falling back to standard signMessage:", error.message);
|
|
3204
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
3205
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
3205
3206
|
} else {
|
|
3206
3207
|
throw error;
|
|
3207
3208
|
}
|
package/dist/expo/index.d.mts
CHANGED
|
@@ -695,17 +695,24 @@ interface MemoryExtractionResult {
|
|
|
695
695
|
items: MemoryItem$1[];
|
|
696
696
|
}
|
|
697
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Options for signing messages.
|
|
700
|
+
*/
|
|
701
|
+
interface SignMessageOptions {
|
|
702
|
+
/** Whether to show wallet UI during signing. Default: true */
|
|
703
|
+
showWalletUIs?: boolean;
|
|
704
|
+
}
|
|
698
705
|
/**
|
|
699
706
|
* Type for the signMessage function that client must provide.
|
|
700
|
-
* This is typically from Privy's useSignMessage hook
|
|
707
|
+
* This is typically from Privy's useSignMessage hook.
|
|
701
708
|
*/
|
|
702
|
-
type SignMessageFn = (message: string) => Promise<string>;
|
|
709
|
+
type SignMessageFn = (message: string, options?: SignMessageOptions) => Promise<string>;
|
|
703
710
|
/**
|
|
704
711
|
* Type for embedded wallet signer function that enables silent signing.
|
|
705
712
|
* For Privy embedded wallets, this can sign programmatically without user interaction
|
|
706
713
|
* when configured correctly in the Privy dashboard.
|
|
707
714
|
*/
|
|
708
|
-
type EmbeddedWalletSignerFn = (message: string) => Promise<string>;
|
|
715
|
+
type EmbeddedWalletSignerFn = (message: string, options?: SignMessageOptions) => Promise<string>;
|
|
709
716
|
|
|
710
717
|
type MemoryType = "identity" | "preference" | "project" | "skill" | "constraint";
|
|
711
718
|
interface MemoryItem {
|
package/dist/expo/index.d.ts
CHANGED
|
@@ -695,17 +695,24 @@ interface MemoryExtractionResult {
|
|
|
695
695
|
items: MemoryItem$1[];
|
|
696
696
|
}
|
|
697
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Options for signing messages.
|
|
700
|
+
*/
|
|
701
|
+
interface SignMessageOptions {
|
|
702
|
+
/** Whether to show wallet UI during signing. Default: true */
|
|
703
|
+
showWalletUIs?: boolean;
|
|
704
|
+
}
|
|
698
705
|
/**
|
|
699
706
|
* Type for the signMessage function that client must provide.
|
|
700
|
-
* This is typically from Privy's useSignMessage hook
|
|
707
|
+
* This is typically from Privy's useSignMessage hook.
|
|
701
708
|
*/
|
|
702
|
-
type SignMessageFn = (message: string) => Promise<string>;
|
|
709
|
+
type SignMessageFn = (message: string, options?: SignMessageOptions) => Promise<string>;
|
|
703
710
|
/**
|
|
704
711
|
* Type for embedded wallet signer function that enables silent signing.
|
|
705
712
|
* For Privy embedded wallets, this can sign programmatically without user interaction
|
|
706
713
|
* when configured correctly in the Privy dashboard.
|
|
707
714
|
*/
|
|
708
|
-
type EmbeddedWalletSignerFn = (message: string) => Promise<string>;
|
|
715
|
+
type EmbeddedWalletSignerFn = (message: string, options?: SignMessageOptions) => Promise<string>;
|
|
709
716
|
|
|
710
717
|
type MemoryType = "identity" | "preference" | "project" | "skill" | "constraint";
|
|
711
718
|
interface MemoryItem {
|
package/dist/expo/index.mjs
CHANGED
|
@@ -3155,17 +3155,18 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
|
|
|
3155
3155
|
if (existingKey) {
|
|
3156
3156
|
return;
|
|
3157
3157
|
}
|
|
3158
|
+
const signOptions = { showWalletUIs: false };
|
|
3158
3159
|
let signature;
|
|
3159
3160
|
try {
|
|
3160
3161
|
if (embeddedWalletSigner) {
|
|
3161
|
-
signature = await embeddedWalletSigner(SIGN_MESSAGE);
|
|
3162
|
+
signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
|
|
3162
3163
|
} else {
|
|
3163
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
3164
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
3164
3165
|
}
|
|
3165
3166
|
} catch (error) {
|
|
3166
3167
|
if (embeddedWalletSigner && error instanceof Error) {
|
|
3167
3168
|
console.warn("Embedded wallet signing failed, falling back to standard signMessage:", error.message);
|
|
3168
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
3169
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
3169
3170
|
} else {
|
|
3170
3171
|
throw error;
|
|
3171
3172
|
}
|
package/dist/react/index.cjs
CHANGED
|
@@ -42,6 +42,7 @@ __export(index_exports, {
|
|
|
42
42
|
BACKUP_DRIVE_ROOT_FOLDER: () => DEFAULT_ROOT_FOLDER,
|
|
43
43
|
BACKUP_ICLOUD_FOLDER: () => DEFAULT_BACKUP_FOLDER2,
|
|
44
44
|
BackupAuthProvider: () => BackupAuthProvider,
|
|
45
|
+
BlobUrlManager: () => BlobUrlManager,
|
|
45
46
|
ChatConversation: () => Conversation,
|
|
46
47
|
ChatMessage: () => Message,
|
|
47
48
|
DEFAULT_BACKUP_FOLDER: () => DEFAULT_BACKUP_FOLDER,
|
|
@@ -71,9 +72,11 @@ __export(index_exports, {
|
|
|
71
72
|
createMemoryContextSystemMessage: () => createMemoryContextSystemMessage,
|
|
72
73
|
decryptData: () => decryptData,
|
|
73
74
|
decryptDataBytes: () => decryptDataBytes,
|
|
75
|
+
deleteEncryptedFile: () => deleteEncryptedFile,
|
|
74
76
|
encryptData: () => encryptData,
|
|
75
77
|
exportPublicKey: () => exportPublicKey,
|
|
76
78
|
extractConversationContext: () => extractConversationContext,
|
|
79
|
+
fileExists: () => fileExists,
|
|
77
80
|
findFileIdBySourceUrl: () => findFileIdBySourceUrl,
|
|
78
81
|
formatMemoriesForChat: () => formatMemoriesForChat,
|
|
79
82
|
generateCompositeKey: () => generateCompositeKey,
|
|
@@ -85,6 +88,7 @@ __export(index_exports, {
|
|
|
85
88
|
getAndClearDriveReturnUrl: () => getAndClearDriveReturnUrl,
|
|
86
89
|
getCalendarAccessToken: () => getCalendarAccessToken,
|
|
87
90
|
getDriveAccessToken: () => getDriveAccessToken,
|
|
91
|
+
getEncryptionKey: () => getEncryptionKey,
|
|
88
92
|
getGoogleDriveStoredToken: () => getGoogleDriveStoredToken,
|
|
89
93
|
getValidCalendarToken: () => getValidCalendarToken,
|
|
90
94
|
getValidDriveToken: () => getValidDriveToken,
|
|
@@ -99,7 +103,9 @@ __export(index_exports, {
|
|
|
99
103
|
hasKeyPair: () => hasKeyPair,
|
|
100
104
|
isCalendarCallback: () => isCalendarCallback,
|
|
101
105
|
isDriveCallback: () => isDriveCallback,
|
|
106
|
+
isOPFSSupported: () => isOPFSSupported,
|
|
102
107
|
memoryStorageSchema: () => memoryStorageSchema,
|
|
108
|
+
readEncryptedFile: () => readEncryptedFile,
|
|
103
109
|
refreshCalendarToken: () => refreshCalendarToken,
|
|
104
110
|
refreshDriveToken: () => refreshDriveToken,
|
|
105
111
|
replaceUrlWithMCPPlaceholder: () => replaceUrlWithMCPPlaceholder,
|
|
@@ -137,7 +143,8 @@ __export(index_exports, {
|
|
|
137
143
|
usePdf: () => usePdf,
|
|
138
144
|
useSearch: () => useSearch,
|
|
139
145
|
useSettings: () => useSettings,
|
|
140
|
-
userPreferencesStorageSchema: () => userPreferencesStorageSchema
|
|
146
|
+
userPreferencesStorageSchema: () => userPreferencesStorageSchema,
|
|
147
|
+
writeEncryptedFile: () => writeEncryptedFile
|
|
141
148
|
});
|
|
142
149
|
module.exports = __toCommonJS(index_exports);
|
|
143
150
|
|
|
@@ -2162,17 +2169,18 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
|
|
|
2162
2169
|
if (existingKey) {
|
|
2163
2170
|
return;
|
|
2164
2171
|
}
|
|
2172
|
+
const signOptions = { showWalletUIs: false };
|
|
2165
2173
|
let signature;
|
|
2166
2174
|
try {
|
|
2167
2175
|
if (embeddedWalletSigner) {
|
|
2168
|
-
signature = await embeddedWalletSigner(SIGN_MESSAGE);
|
|
2176
|
+
signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
|
|
2169
2177
|
} else {
|
|
2170
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
2178
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
2171
2179
|
}
|
|
2172
2180
|
} catch (error) {
|
|
2173
2181
|
if (embeddedWalletSigner && error instanceof Error) {
|
|
2174
2182
|
console.warn("Embedded wallet signing failed, falling back to standard signMessage:", error.message);
|
|
2175
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
2183
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
2176
2184
|
} else {
|
|
2177
2185
|
throw error;
|
|
2178
2186
|
}
|
|
@@ -2332,17 +2340,18 @@ async function requestKeyPair(walletAddress, signMessage, embeddedWalletSigner)
|
|
|
2332
2340
|
`Failed to load persisted keypair, generating new one: ${error instanceof Error ? error.message : String(error)}`
|
|
2333
2341
|
);
|
|
2334
2342
|
}
|
|
2343
|
+
const signOptions = { showWalletUIs: false };
|
|
2335
2344
|
let signature;
|
|
2336
2345
|
try {
|
|
2337
2346
|
if (embeddedWalletSigner) {
|
|
2338
|
-
signature = await embeddedWalletSigner(SIGN_MESSAGE);
|
|
2347
|
+
signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
|
|
2339
2348
|
} else {
|
|
2340
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
2349
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
2341
2350
|
}
|
|
2342
2351
|
} catch (error) {
|
|
2343
2352
|
if (embeddedWalletSigner && error instanceof Error) {
|
|
2344
2353
|
console.warn("Embedded wallet signing failed, falling back to standard signMessage:", error.message);
|
|
2345
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
2354
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
2346
2355
|
} else {
|
|
2347
2356
|
throw error;
|
|
2348
2357
|
}
|
|
@@ -2829,6 +2838,178 @@ async function searchMessagesOp(ctx, queryVector, options) {
|
|
|
2829
2838
|
return resultsWithSimilarity.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
2830
2839
|
}
|
|
2831
2840
|
|
|
2841
|
+
// src/lib/storage/opfs.ts
|
|
2842
|
+
var FILE_PLACEHOLDER_PREFIX = "__SDKFILE__";
|
|
2843
|
+
var FILE_PLACEHOLDER_SUFFIX = "__";
|
|
2844
|
+
var FILE_PLACEHOLDER_REGEX = /__SDKFILE__([a-f0-9-]+)__/g;
|
|
2845
|
+
function createFilePlaceholder(fileId) {
|
|
2846
|
+
return `${FILE_PLACEHOLDER_PREFIX}${fileId}${FILE_PLACEHOLDER_SUFFIX}`;
|
|
2847
|
+
}
|
|
2848
|
+
function extractFileIds(content) {
|
|
2849
|
+
const matches = content.matchAll(FILE_PLACEHOLDER_REGEX);
|
|
2850
|
+
return Array.from(matches, (m) => m[1]);
|
|
2851
|
+
}
|
|
2852
|
+
function isOPFSSupported() {
|
|
2853
|
+
return typeof navigator !== "undefined" && "storage" in navigator && "getDirectory" in navigator.storage;
|
|
2854
|
+
}
|
|
2855
|
+
async function getSDKDirectory() {
|
|
2856
|
+
const root = await navigator.storage.getDirectory();
|
|
2857
|
+
return root.getDirectoryHandle("reverbia-sdk-files", { create: true });
|
|
2858
|
+
}
|
|
2859
|
+
function bytesToHex2(bytes) {
|
|
2860
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2861
|
+
}
|
|
2862
|
+
function hexToBytes2(hex) {
|
|
2863
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
2864
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
2865
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
2866
|
+
}
|
|
2867
|
+
return bytes;
|
|
2868
|
+
}
|
|
2869
|
+
async function encryptBlob(blob, encryptionKey) {
|
|
2870
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
2871
|
+
const plaintext = new Uint8Array(arrayBuffer);
|
|
2872
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
2873
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
2874
|
+
{ name: "AES-GCM", iv },
|
|
2875
|
+
encryptionKey,
|
|
2876
|
+
plaintext
|
|
2877
|
+
);
|
|
2878
|
+
const combined = new Uint8Array(iv.length + ciphertext.byteLength);
|
|
2879
|
+
combined.set(iv, 0);
|
|
2880
|
+
combined.set(new Uint8Array(ciphertext), iv.length);
|
|
2881
|
+
return bytesToHex2(combined);
|
|
2882
|
+
}
|
|
2883
|
+
async function decryptToBytes(encryptedHex, encryptionKey) {
|
|
2884
|
+
const combined = hexToBytes2(encryptedHex);
|
|
2885
|
+
const iv = combined.slice(0, 12);
|
|
2886
|
+
const ciphertext = combined.slice(12);
|
|
2887
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
2888
|
+
{ name: "AES-GCM", iv },
|
|
2889
|
+
encryptionKey,
|
|
2890
|
+
ciphertext
|
|
2891
|
+
);
|
|
2892
|
+
return new Uint8Array(decrypted);
|
|
2893
|
+
}
|
|
2894
|
+
async function writeEncryptedFile(fileId, blob, encryptionKey, metadata) {
|
|
2895
|
+
if (!isOPFSSupported()) {
|
|
2896
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2897
|
+
}
|
|
2898
|
+
const dir = await getSDKDirectory();
|
|
2899
|
+
const encryptedHex = await encryptBlob(blob, encryptionKey);
|
|
2900
|
+
const contentHandle = await dir.getFileHandle(`${fileId}.enc`, {
|
|
2901
|
+
create: true
|
|
2902
|
+
});
|
|
2903
|
+
const contentWritable = await contentHandle.createWritable();
|
|
2904
|
+
await contentWritable.write(encryptedHex);
|
|
2905
|
+
await contentWritable.close();
|
|
2906
|
+
const fileMetadata = {
|
|
2907
|
+
id: fileId,
|
|
2908
|
+
name: metadata?.name || `file-${fileId}`,
|
|
2909
|
+
type: blob.type || "application/octet-stream",
|
|
2910
|
+
size: blob.size,
|
|
2911
|
+
sourceUrl: metadata?.sourceUrl,
|
|
2912
|
+
createdAt: Date.now()
|
|
2913
|
+
};
|
|
2914
|
+
const metaHandle = await dir.getFileHandle(`${fileId}.meta.json`, {
|
|
2915
|
+
create: true
|
|
2916
|
+
});
|
|
2917
|
+
const metaWritable = await metaHandle.createWritable();
|
|
2918
|
+
await metaWritable.write(JSON.stringify(fileMetadata));
|
|
2919
|
+
await metaWritable.close();
|
|
2920
|
+
}
|
|
2921
|
+
async function readEncryptedFile(fileId, encryptionKey) {
|
|
2922
|
+
if (!isOPFSSupported()) {
|
|
2923
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2924
|
+
}
|
|
2925
|
+
const dir = await getSDKDirectory();
|
|
2926
|
+
try {
|
|
2927
|
+
const contentHandle = await dir.getFileHandle(`${fileId}.enc`);
|
|
2928
|
+
const contentFile = await contentHandle.getFile();
|
|
2929
|
+
const encryptedHex = await contentFile.text();
|
|
2930
|
+
const metaHandle = await dir.getFileHandle(`${fileId}.meta.json`);
|
|
2931
|
+
const metaFile = await metaHandle.getFile();
|
|
2932
|
+
const metadata = JSON.parse(await metaFile.text());
|
|
2933
|
+
const decryptedBytes = await decryptToBytes(encryptedHex, encryptionKey);
|
|
2934
|
+
const blob = new Blob([decryptedBytes.buffer], { type: metadata.type });
|
|
2935
|
+
return { blob, metadata };
|
|
2936
|
+
} catch (error) {
|
|
2937
|
+
if (error instanceof DOMException && error.name === "NotFoundError") {
|
|
2938
|
+
return null;
|
|
2939
|
+
}
|
|
2940
|
+
throw error;
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
async function deleteEncryptedFile(fileId) {
|
|
2944
|
+
if (!isOPFSSupported()) {
|
|
2945
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2946
|
+
}
|
|
2947
|
+
const dir = await getSDKDirectory();
|
|
2948
|
+
try {
|
|
2949
|
+
await dir.removeEntry(`${fileId}.enc`);
|
|
2950
|
+
await dir.removeEntry(`${fileId}.meta.json`);
|
|
2951
|
+
} catch {
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
async function fileExists(fileId) {
|
|
2955
|
+
if (!isOPFSSupported()) {
|
|
2956
|
+
return false;
|
|
2957
|
+
}
|
|
2958
|
+
const dir = await getSDKDirectory();
|
|
2959
|
+
try {
|
|
2960
|
+
await dir.getFileHandle(`${fileId}.enc`);
|
|
2961
|
+
return true;
|
|
2962
|
+
} catch {
|
|
2963
|
+
return false;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
var BlobUrlManager = class {
|
|
2967
|
+
constructor() {
|
|
2968
|
+
this.activeUrls = /* @__PURE__ */ new Map();
|
|
2969
|
+
}
|
|
2970
|
+
// fileId -> blobUrl
|
|
2971
|
+
/**
|
|
2972
|
+
* Creates a blob URL for a file and tracks it.
|
|
2973
|
+
*/
|
|
2974
|
+
createUrl(fileId, blob) {
|
|
2975
|
+
this.revokeUrl(fileId);
|
|
2976
|
+
const url = URL.createObjectURL(blob);
|
|
2977
|
+
this.activeUrls.set(fileId, url);
|
|
2978
|
+
return url;
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Gets the active blob URL for a file, if any.
|
|
2982
|
+
*/
|
|
2983
|
+
getUrl(fileId) {
|
|
2984
|
+
return this.activeUrls.get(fileId);
|
|
2985
|
+
}
|
|
2986
|
+
/**
|
|
2987
|
+
* Revokes a blob URL and removes it from tracking.
|
|
2988
|
+
*/
|
|
2989
|
+
revokeUrl(fileId) {
|
|
2990
|
+
const url = this.activeUrls.get(fileId);
|
|
2991
|
+
if (url) {
|
|
2992
|
+
URL.revokeObjectURL(url);
|
|
2993
|
+
this.activeUrls.delete(fileId);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
/**
|
|
2997
|
+
* Revokes all tracked blob URLs.
|
|
2998
|
+
*/
|
|
2999
|
+
revokeAll() {
|
|
3000
|
+
for (const url of this.activeUrls.values()) {
|
|
3001
|
+
URL.revokeObjectURL(url);
|
|
3002
|
+
}
|
|
3003
|
+
this.activeUrls.clear();
|
|
3004
|
+
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Gets the count of active blob URLs.
|
|
3007
|
+
*/
|
|
3008
|
+
get size() {
|
|
3009
|
+
return this.activeUrls.size;
|
|
3010
|
+
}
|
|
3011
|
+
};
|
|
3012
|
+
|
|
2832
3013
|
// src/react/useChatStorage.ts
|
|
2833
3014
|
function replaceUrlWithMCPPlaceholder(content, url, fileId) {
|
|
2834
3015
|
const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -2850,20 +3031,69 @@ function replaceUrlWithMCPPlaceholder(content, url, fileId) {
|
|
|
2850
3031
|
function findFileIdBySourceUrl(files, sourceUrl) {
|
|
2851
3032
|
return files?.find((f) => f.sourceUrl === sourceUrl)?.id;
|
|
2852
3033
|
}
|
|
2853
|
-
function
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
3034
|
+
async function isUrlValid(url, timeoutMs = 5e3) {
|
|
3035
|
+
try {
|
|
3036
|
+
const controller = new AbortController();
|
|
3037
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
3038
|
+
const response = await fetch(url, {
|
|
3039
|
+
method: "GET",
|
|
3040
|
+
headers: { Range: "bytes=0-0" },
|
|
3041
|
+
signal: controller.signal
|
|
3042
|
+
});
|
|
3043
|
+
clearTimeout(timeoutId);
|
|
3044
|
+
return response.ok || response.status === 206;
|
|
3045
|
+
} catch {
|
|
3046
|
+
return false;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
async function storedToLlmapiMessage(stored) {
|
|
3050
|
+
let textContent = stored.content;
|
|
3051
|
+
const fileUrlMap = /* @__PURE__ */ new Map();
|
|
3052
|
+
const imageParts = [];
|
|
2857
3053
|
if (stored.files?.length) {
|
|
2858
3054
|
for (const file of stored.files) {
|
|
2859
3055
|
if (file.url) {
|
|
2860
|
-
|
|
3056
|
+
imageParts.push({
|
|
2861
3057
|
type: "image_url",
|
|
2862
3058
|
image_url: { url: file.url }
|
|
2863
3059
|
});
|
|
3060
|
+
} else if (file.sourceUrl) {
|
|
3061
|
+
const isValid = await isUrlValid(file.sourceUrl);
|
|
3062
|
+
if (isValid) {
|
|
3063
|
+
imageParts.push({
|
|
3064
|
+
type: "image_url",
|
|
3065
|
+
image_url: { url: file.sourceUrl }
|
|
3066
|
+
});
|
|
3067
|
+
fileUrlMap.set(file.id, file.sourceUrl);
|
|
3068
|
+
}
|
|
2864
3069
|
}
|
|
2865
3070
|
}
|
|
2866
3071
|
}
|
|
3072
|
+
textContent = textContent.replace(
|
|
3073
|
+
/__SDKFILE__([a-f0-9-]+)__/g,
|
|
3074
|
+
(match, fileId) => {
|
|
3075
|
+
const sourceUrl = fileUrlMap.get(fileId);
|
|
3076
|
+
if (sourceUrl) {
|
|
3077
|
+
return ``;
|
|
3078
|
+
}
|
|
3079
|
+
return "";
|
|
3080
|
+
}
|
|
3081
|
+
);
|
|
3082
|
+
textContent = textContent.replace(
|
|
3083
|
+
/!\[MCP_IMAGE:([a-f0-9-]+)\]/g,
|
|
3084
|
+
(match, fileId) => {
|
|
3085
|
+
const sourceUrl = fileUrlMap.get(fileId);
|
|
3086
|
+
if (sourceUrl) {
|
|
3087
|
+
return ``;
|
|
3088
|
+
}
|
|
3089
|
+
return "";
|
|
3090
|
+
}
|
|
3091
|
+
);
|
|
3092
|
+
textContent = textContent.replace(/\n{3,}/g, "\n\n").trim();
|
|
3093
|
+
const content = [
|
|
3094
|
+
{ type: "text", text: textContent },
|
|
3095
|
+
...imageParts
|
|
3096
|
+
];
|
|
2867
3097
|
return {
|
|
2868
3098
|
role: stored.role,
|
|
2869
3099
|
content
|
|
@@ -2880,9 +3110,17 @@ function useChatStorage(options) {
|
|
|
2880
3110
|
onData,
|
|
2881
3111
|
onFinish,
|
|
2882
3112
|
onError,
|
|
2883
|
-
apiType
|
|
3113
|
+
apiType,
|
|
3114
|
+
walletAddress
|
|
2884
3115
|
} = options;
|
|
2885
3116
|
const [currentConversationId, setCurrentConversationId] = (0, import_react2.useState)(initialConversationId || null);
|
|
3117
|
+
const blobManagerRef = (0, import_react2.useRef)(new BlobUrlManager());
|
|
3118
|
+
(0, import_react2.useEffect)(() => {
|
|
3119
|
+
const manager = blobManagerRef.current;
|
|
3120
|
+
return () => {
|
|
3121
|
+
manager.revokeAll();
|
|
3122
|
+
};
|
|
3123
|
+
}, []);
|
|
2886
3124
|
const messagesCollection = (0, import_react2.useMemo)(
|
|
2887
3125
|
() => database.get("history"),
|
|
2888
3126
|
[database]
|
|
@@ -2950,9 +3188,49 @@ function useChatStorage(options) {
|
|
|
2950
3188
|
);
|
|
2951
3189
|
const getMessages = (0, import_react2.useCallback)(
|
|
2952
3190
|
async (convId) => {
|
|
2953
|
-
|
|
3191
|
+
const messages = await getMessagesOp(storageCtx, convId);
|
|
3192
|
+
if (walletAddress && hasEncryptionKey(walletAddress) && isOPFSSupported()) {
|
|
3193
|
+
try {
|
|
3194
|
+
const encryptionKey = await getEncryptionKey(walletAddress);
|
|
3195
|
+
const blobManager = blobManagerRef.current;
|
|
3196
|
+
const resolvedMessages = await Promise.all(
|
|
3197
|
+
messages.map(async (msg) => {
|
|
3198
|
+
const fileIds = extractFileIds(msg.content);
|
|
3199
|
+
if (fileIds.length === 0) {
|
|
3200
|
+
return msg;
|
|
3201
|
+
}
|
|
3202
|
+
let resolvedContent = msg.content;
|
|
3203
|
+
for (const fileId of fileIds) {
|
|
3204
|
+
let url = blobManager.getUrl(fileId);
|
|
3205
|
+
if (!url) {
|
|
3206
|
+
const result = await readEncryptedFile(fileId, encryptionKey);
|
|
3207
|
+
if (result) {
|
|
3208
|
+
url = blobManager.createUrl(fileId, result.blob);
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
if (url) {
|
|
3212
|
+
const placeholder = createFilePlaceholder(fileId);
|
|
3213
|
+
resolvedContent = resolvedContent.replace(
|
|
3214
|
+
new RegExp(
|
|
3215
|
+
placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
|
|
3216
|
+
"g"
|
|
3217
|
+
),
|
|
3218
|
+
``
|
|
3219
|
+
);
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
return { ...msg, content: resolvedContent };
|
|
3223
|
+
})
|
|
3224
|
+
);
|
|
3225
|
+
return resolvedMessages;
|
|
3226
|
+
} catch (error) {
|
|
3227
|
+
console.error("[useChatStorage] Failed to resolve file placeholders:", error);
|
|
3228
|
+
return messages;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
return messages;
|
|
2954
3232
|
},
|
|
2955
|
-
[storageCtx]
|
|
3233
|
+
[storageCtx, walletAddress]
|
|
2956
3234
|
);
|
|
2957
3235
|
const getMessageCount = (0, import_react2.useCallback)(
|
|
2958
3236
|
async (convId) => {
|
|
@@ -3176,6 +3454,89 @@ function useChatStorage(options) {
|
|
|
3176
3454
|
},
|
|
3177
3455
|
[]
|
|
3178
3456
|
);
|
|
3457
|
+
const extractAndStoreEncryptedMCPImages = (0, import_react2.useCallback)(
|
|
3458
|
+
async (content, address) => {
|
|
3459
|
+
try {
|
|
3460
|
+
if (!isOPFSSupported()) {
|
|
3461
|
+
console.warn("[extractAndStoreEncryptedMCPImages] OPFS not supported");
|
|
3462
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3463
|
+
}
|
|
3464
|
+
if (!hasEncryptionKey(address)) {
|
|
3465
|
+
console.warn("[extractAndStoreEncryptedMCPImages] Encryption key not available");
|
|
3466
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3467
|
+
}
|
|
3468
|
+
const MCP_IMAGE_URL_PATTERN = new RegExp(
|
|
3469
|
+
`https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
|
|
3470
|
+
"g"
|
|
3471
|
+
);
|
|
3472
|
+
const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
|
|
3473
|
+
if (!urlMatches || urlMatches.length === 0) {
|
|
3474
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3475
|
+
}
|
|
3476
|
+
const uniqueUrls = [...new Set(urlMatches)];
|
|
3477
|
+
const encryptionKey = await getEncryptionKey(address);
|
|
3478
|
+
const processedFiles = [];
|
|
3479
|
+
let cleanedContent = content;
|
|
3480
|
+
const results = await Promise.allSettled(
|
|
3481
|
+
uniqueUrls.map(async (imageUrl) => {
|
|
3482
|
+
const controller = new AbortController();
|
|
3483
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
3484
|
+
try {
|
|
3485
|
+
const response = await fetch(imageUrl, {
|
|
3486
|
+
signal: controller.signal,
|
|
3487
|
+
cache: "no-store"
|
|
3488
|
+
});
|
|
3489
|
+
if (!response.ok) {
|
|
3490
|
+
throw new Error(`Failed to fetch image: ${response.status}`);
|
|
3491
|
+
}
|
|
3492
|
+
const blob = await response.blob();
|
|
3493
|
+
const fileId = crypto.randomUUID();
|
|
3494
|
+
const urlPath = imageUrl.split("?")[0] ?? imageUrl;
|
|
3495
|
+
const extension = urlPath.match(/\.([a-zA-Z0-9]+)$/)?.[1] || "png";
|
|
3496
|
+
const mimeType = blob.type || `image/${extension}`;
|
|
3497
|
+
const fileName = `mcp-image-${Date.now()}-${fileId.slice(0, 8)}.${extension}`;
|
|
3498
|
+
await writeEncryptedFile(fileId, blob, encryptionKey, {
|
|
3499
|
+
name: fileName,
|
|
3500
|
+
sourceUrl: imageUrl
|
|
3501
|
+
});
|
|
3502
|
+
return { fileId, fileName, mimeType, size: blob.size, imageUrl };
|
|
3503
|
+
} finally {
|
|
3504
|
+
clearTimeout(timeoutId);
|
|
3505
|
+
}
|
|
3506
|
+
})
|
|
3507
|
+
);
|
|
3508
|
+
results.forEach((result, i) => {
|
|
3509
|
+
const imageUrl = uniqueUrls[i];
|
|
3510
|
+
if (result.status === "fulfilled") {
|
|
3511
|
+
const { fileId, fileName, mimeType, size } = result.value;
|
|
3512
|
+
processedFiles.push({
|
|
3513
|
+
id: fileId,
|
|
3514
|
+
name: fileName,
|
|
3515
|
+
type: mimeType,
|
|
3516
|
+
size,
|
|
3517
|
+
sourceUrl: imageUrl
|
|
3518
|
+
});
|
|
3519
|
+
const placeholder = createFilePlaceholder(fileId);
|
|
3520
|
+
const escapedUrl = imageUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3521
|
+
const markdownImagePattern = new RegExp(
|
|
3522
|
+
`!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
|
|
3523
|
+
"g"
|
|
3524
|
+
);
|
|
3525
|
+
cleanedContent = cleanedContent.replace(markdownImagePattern, placeholder);
|
|
3526
|
+
cleanedContent = cleanedContent.replace(new RegExp(escapedUrl, "g"), placeholder);
|
|
3527
|
+
} else {
|
|
3528
|
+
console.error("[extractAndStoreEncryptedMCPImages] Failed:", result.reason);
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
cleanedContent = cleanedContent.replace(/\n{3,}/g, "\n\n").trim();
|
|
3532
|
+
return { processedFiles, cleanedContent };
|
|
3533
|
+
} catch (err) {
|
|
3534
|
+
console.error("[extractAndStoreEncryptedMCPImages] Unexpected error:", err);
|
|
3535
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3536
|
+
}
|
|
3537
|
+
},
|
|
3538
|
+
[]
|
|
3539
|
+
);
|
|
3179
3540
|
const sendMessage = (0, import_react2.useCallback)(
|
|
3180
3541
|
async (args) => {
|
|
3181
3542
|
const {
|
|
@@ -3227,10 +3588,10 @@ function useChatStorage(options) {
|
|
|
3227
3588
|
const storedMessages = await getMessages(convId);
|
|
3228
3589
|
const validMessages = storedMessages.filter((msg) => !msg.error);
|
|
3229
3590
|
const limitedMessages = validMessages.slice(-maxHistoryMessages);
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
];
|
|
3591
|
+
const historyMessages = await Promise.all(
|
|
3592
|
+
limitedMessages.map(storedToLlmapiMessage)
|
|
3593
|
+
);
|
|
3594
|
+
messagesToSend = [...historyMessages, ...messages];
|
|
3234
3595
|
} else {
|
|
3235
3596
|
messagesToSend = [...messages];
|
|
3236
3597
|
}
|
|
@@ -3368,7 +3729,14 @@ function useChatStorage(options) {
|
|
|
3368
3729
|
let cleanedContent = assistantContent.replace(jsonSourcesBlockRegex, "").trim();
|
|
3369
3730
|
cleanedContent = cleanedContent.replace(/\n{3,}/g, "\n\n");
|
|
3370
3731
|
let processedFiles = [];
|
|
3371
|
-
if (
|
|
3732
|
+
if (walletAddress) {
|
|
3733
|
+
const result2 = await extractAndStoreEncryptedMCPImages(
|
|
3734
|
+
cleanedContent,
|
|
3735
|
+
walletAddress
|
|
3736
|
+
);
|
|
3737
|
+
processedFiles = result2.processedFiles;
|
|
3738
|
+
cleanedContent = result2.cleanedContent;
|
|
3739
|
+
} else if (writeFile) {
|
|
3372
3740
|
const result2 = await extractAndStoreMCPImages(
|
|
3373
3741
|
cleanedContent,
|
|
3374
3742
|
writeFile
|
|
@@ -3404,7 +3772,7 @@ function useChatStorage(options) {
|
|
|
3404
3772
|
assistantMessage: storedAssistantMessage
|
|
3405
3773
|
};
|
|
3406
3774
|
},
|
|
3407
|
-
[ensureConversation, getMessages, storageCtx, baseSendMessage]
|
|
3775
|
+
[ensureConversation, getMessages, storageCtx, baseSendMessage, walletAddress, extractAndStoreEncryptedMCPImages]
|
|
3408
3776
|
);
|
|
3409
3777
|
const searchMessages = (0, import_react2.useCallback)(
|
|
3410
3778
|
async (queryVector, options2) => {
|
|
@@ -9179,6 +9547,7 @@ function hasDriveCredentials() {
|
|
|
9179
9547
|
BACKUP_DRIVE_ROOT_FOLDER,
|
|
9180
9548
|
BACKUP_ICLOUD_FOLDER,
|
|
9181
9549
|
BackupAuthProvider,
|
|
9550
|
+
BlobUrlManager,
|
|
9182
9551
|
ChatConversation,
|
|
9183
9552
|
ChatMessage,
|
|
9184
9553
|
DEFAULT_BACKUP_FOLDER,
|
|
@@ -9208,9 +9577,11 @@ function hasDriveCredentials() {
|
|
|
9208
9577
|
createMemoryContextSystemMessage,
|
|
9209
9578
|
decryptData,
|
|
9210
9579
|
decryptDataBytes,
|
|
9580
|
+
deleteEncryptedFile,
|
|
9211
9581
|
encryptData,
|
|
9212
9582
|
exportPublicKey,
|
|
9213
9583
|
extractConversationContext,
|
|
9584
|
+
fileExists,
|
|
9214
9585
|
findFileIdBySourceUrl,
|
|
9215
9586
|
formatMemoriesForChat,
|
|
9216
9587
|
generateCompositeKey,
|
|
@@ -9222,6 +9593,7 @@ function hasDriveCredentials() {
|
|
|
9222
9593
|
getAndClearDriveReturnUrl,
|
|
9223
9594
|
getCalendarAccessToken,
|
|
9224
9595
|
getDriveAccessToken,
|
|
9596
|
+
getEncryptionKey,
|
|
9225
9597
|
getGoogleDriveStoredToken,
|
|
9226
9598
|
getValidCalendarToken,
|
|
9227
9599
|
getValidDriveToken,
|
|
@@ -9236,7 +9608,9 @@ function hasDriveCredentials() {
|
|
|
9236
9608
|
hasKeyPair,
|
|
9237
9609
|
isCalendarCallback,
|
|
9238
9610
|
isDriveCallback,
|
|
9611
|
+
isOPFSSupported,
|
|
9239
9612
|
memoryStorageSchema,
|
|
9613
|
+
readEncryptedFile,
|
|
9240
9614
|
refreshCalendarToken,
|
|
9241
9615
|
refreshDriveToken,
|
|
9242
9616
|
replaceUrlWithMCPPlaceholder,
|
|
@@ -9274,5 +9648,6 @@ function hasDriveCredentials() {
|
|
|
9274
9648
|
usePdf,
|
|
9275
9649
|
useSearch,
|
|
9276
9650
|
useSettings,
|
|
9277
|
-
userPreferencesStorageSchema
|
|
9651
|
+
userPreferencesStorageSchema,
|
|
9652
|
+
writeEncryptedFile
|
|
9278
9653
|
});
|