@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/react/index.mjs
CHANGED
|
@@ -2030,17 +2030,18 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
|
|
|
2030
2030
|
if (existingKey) {
|
|
2031
2031
|
return;
|
|
2032
2032
|
}
|
|
2033
|
+
const signOptions = { showWalletUIs: false };
|
|
2033
2034
|
let signature;
|
|
2034
2035
|
try {
|
|
2035
2036
|
if (embeddedWalletSigner) {
|
|
2036
|
-
signature = await embeddedWalletSigner(SIGN_MESSAGE);
|
|
2037
|
+
signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
|
|
2037
2038
|
} else {
|
|
2038
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
2039
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
2039
2040
|
}
|
|
2040
2041
|
} catch (error) {
|
|
2041
2042
|
if (embeddedWalletSigner && error instanceof Error) {
|
|
2042
2043
|
console.warn("Embedded wallet signing failed, falling back to standard signMessage:", error.message);
|
|
2043
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
2044
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
2044
2045
|
} else {
|
|
2045
2046
|
throw error;
|
|
2046
2047
|
}
|
|
@@ -2200,17 +2201,18 @@ async function requestKeyPair(walletAddress, signMessage, embeddedWalletSigner)
|
|
|
2200
2201
|
`Failed to load persisted keypair, generating new one: ${error instanceof Error ? error.message : String(error)}`
|
|
2201
2202
|
);
|
|
2202
2203
|
}
|
|
2204
|
+
const signOptions = { showWalletUIs: false };
|
|
2203
2205
|
let signature;
|
|
2204
2206
|
try {
|
|
2205
2207
|
if (embeddedWalletSigner) {
|
|
2206
|
-
signature = await embeddedWalletSigner(SIGN_MESSAGE);
|
|
2208
|
+
signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
|
|
2207
2209
|
} else {
|
|
2208
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
2210
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
2209
2211
|
}
|
|
2210
2212
|
} catch (error) {
|
|
2211
2213
|
if (embeddedWalletSigner && error instanceof Error) {
|
|
2212
2214
|
console.warn("Embedded wallet signing failed, falling back to standard signMessage:", error.message);
|
|
2213
|
-
signature = await signMessage(SIGN_MESSAGE);
|
|
2215
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
2214
2216
|
} else {
|
|
2215
2217
|
throw error;
|
|
2216
2218
|
}
|
|
@@ -2255,7 +2257,7 @@ function useEncryption(signMessage, embeddedWalletSigner) {
|
|
|
2255
2257
|
}
|
|
2256
2258
|
|
|
2257
2259
|
// src/react/useChatStorage.ts
|
|
2258
|
-
import { useCallback as useCallback2, useState as useState2, useMemo } from "react";
|
|
2260
|
+
import { useCallback as useCallback2, useState as useState2, useMemo, useRef as useRef2, useEffect as useEffect2 } from "react";
|
|
2259
2261
|
|
|
2260
2262
|
// src/lib/db/chat/schema.ts
|
|
2261
2263
|
import { appSchema, tableSchema } from "@nozbe/watermelondb";
|
|
@@ -2700,6 +2702,178 @@ async function searchMessagesOp(ctx, queryVector, options) {
|
|
|
2700
2702
|
return resultsWithSimilarity.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
2701
2703
|
}
|
|
2702
2704
|
|
|
2705
|
+
// src/lib/storage/opfs.ts
|
|
2706
|
+
var FILE_PLACEHOLDER_PREFIX = "__SDKFILE__";
|
|
2707
|
+
var FILE_PLACEHOLDER_SUFFIX = "__";
|
|
2708
|
+
var FILE_PLACEHOLDER_REGEX = /__SDKFILE__([a-f0-9-]+)__/g;
|
|
2709
|
+
function createFilePlaceholder(fileId) {
|
|
2710
|
+
return `${FILE_PLACEHOLDER_PREFIX}${fileId}${FILE_PLACEHOLDER_SUFFIX}`;
|
|
2711
|
+
}
|
|
2712
|
+
function extractFileIds(content) {
|
|
2713
|
+
const matches = content.matchAll(FILE_PLACEHOLDER_REGEX);
|
|
2714
|
+
return Array.from(matches, (m) => m[1]);
|
|
2715
|
+
}
|
|
2716
|
+
function isOPFSSupported() {
|
|
2717
|
+
return typeof navigator !== "undefined" && "storage" in navigator && "getDirectory" in navigator.storage;
|
|
2718
|
+
}
|
|
2719
|
+
async function getSDKDirectory() {
|
|
2720
|
+
const root = await navigator.storage.getDirectory();
|
|
2721
|
+
return root.getDirectoryHandle("reverbia-sdk-files", { create: true });
|
|
2722
|
+
}
|
|
2723
|
+
function bytesToHex2(bytes) {
|
|
2724
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2725
|
+
}
|
|
2726
|
+
function hexToBytes2(hex) {
|
|
2727
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
2728
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
2729
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
2730
|
+
}
|
|
2731
|
+
return bytes;
|
|
2732
|
+
}
|
|
2733
|
+
async function encryptBlob(blob, encryptionKey) {
|
|
2734
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
2735
|
+
const plaintext = new Uint8Array(arrayBuffer);
|
|
2736
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
2737
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
2738
|
+
{ name: "AES-GCM", iv },
|
|
2739
|
+
encryptionKey,
|
|
2740
|
+
plaintext
|
|
2741
|
+
);
|
|
2742
|
+
const combined = new Uint8Array(iv.length + ciphertext.byteLength);
|
|
2743
|
+
combined.set(iv, 0);
|
|
2744
|
+
combined.set(new Uint8Array(ciphertext), iv.length);
|
|
2745
|
+
return bytesToHex2(combined);
|
|
2746
|
+
}
|
|
2747
|
+
async function decryptToBytes(encryptedHex, encryptionKey) {
|
|
2748
|
+
const combined = hexToBytes2(encryptedHex);
|
|
2749
|
+
const iv = combined.slice(0, 12);
|
|
2750
|
+
const ciphertext = combined.slice(12);
|
|
2751
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
2752
|
+
{ name: "AES-GCM", iv },
|
|
2753
|
+
encryptionKey,
|
|
2754
|
+
ciphertext
|
|
2755
|
+
);
|
|
2756
|
+
return new Uint8Array(decrypted);
|
|
2757
|
+
}
|
|
2758
|
+
async function writeEncryptedFile(fileId, blob, encryptionKey, metadata) {
|
|
2759
|
+
if (!isOPFSSupported()) {
|
|
2760
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2761
|
+
}
|
|
2762
|
+
const dir = await getSDKDirectory();
|
|
2763
|
+
const encryptedHex = await encryptBlob(blob, encryptionKey);
|
|
2764
|
+
const contentHandle = await dir.getFileHandle(`${fileId}.enc`, {
|
|
2765
|
+
create: true
|
|
2766
|
+
});
|
|
2767
|
+
const contentWritable = await contentHandle.createWritable();
|
|
2768
|
+
await contentWritable.write(encryptedHex);
|
|
2769
|
+
await contentWritable.close();
|
|
2770
|
+
const fileMetadata = {
|
|
2771
|
+
id: fileId,
|
|
2772
|
+
name: metadata?.name || `file-${fileId}`,
|
|
2773
|
+
type: blob.type || "application/octet-stream",
|
|
2774
|
+
size: blob.size,
|
|
2775
|
+
sourceUrl: metadata?.sourceUrl,
|
|
2776
|
+
createdAt: Date.now()
|
|
2777
|
+
};
|
|
2778
|
+
const metaHandle = await dir.getFileHandle(`${fileId}.meta.json`, {
|
|
2779
|
+
create: true
|
|
2780
|
+
});
|
|
2781
|
+
const metaWritable = await metaHandle.createWritable();
|
|
2782
|
+
await metaWritable.write(JSON.stringify(fileMetadata));
|
|
2783
|
+
await metaWritable.close();
|
|
2784
|
+
}
|
|
2785
|
+
async function readEncryptedFile(fileId, encryptionKey) {
|
|
2786
|
+
if (!isOPFSSupported()) {
|
|
2787
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2788
|
+
}
|
|
2789
|
+
const dir = await getSDKDirectory();
|
|
2790
|
+
try {
|
|
2791
|
+
const contentHandle = await dir.getFileHandle(`${fileId}.enc`);
|
|
2792
|
+
const contentFile = await contentHandle.getFile();
|
|
2793
|
+
const encryptedHex = await contentFile.text();
|
|
2794
|
+
const metaHandle = await dir.getFileHandle(`${fileId}.meta.json`);
|
|
2795
|
+
const metaFile = await metaHandle.getFile();
|
|
2796
|
+
const metadata = JSON.parse(await metaFile.text());
|
|
2797
|
+
const decryptedBytes = await decryptToBytes(encryptedHex, encryptionKey);
|
|
2798
|
+
const blob = new Blob([decryptedBytes.buffer], { type: metadata.type });
|
|
2799
|
+
return { blob, metadata };
|
|
2800
|
+
} catch (error) {
|
|
2801
|
+
if (error instanceof DOMException && error.name === "NotFoundError") {
|
|
2802
|
+
return null;
|
|
2803
|
+
}
|
|
2804
|
+
throw error;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
async function deleteEncryptedFile(fileId) {
|
|
2808
|
+
if (!isOPFSSupported()) {
|
|
2809
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2810
|
+
}
|
|
2811
|
+
const dir = await getSDKDirectory();
|
|
2812
|
+
try {
|
|
2813
|
+
await dir.removeEntry(`${fileId}.enc`);
|
|
2814
|
+
await dir.removeEntry(`${fileId}.meta.json`);
|
|
2815
|
+
} catch {
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
async function fileExists(fileId) {
|
|
2819
|
+
if (!isOPFSSupported()) {
|
|
2820
|
+
return false;
|
|
2821
|
+
}
|
|
2822
|
+
const dir = await getSDKDirectory();
|
|
2823
|
+
try {
|
|
2824
|
+
await dir.getFileHandle(`${fileId}.enc`);
|
|
2825
|
+
return true;
|
|
2826
|
+
} catch {
|
|
2827
|
+
return false;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
var BlobUrlManager = class {
|
|
2831
|
+
constructor() {
|
|
2832
|
+
this.activeUrls = /* @__PURE__ */ new Map();
|
|
2833
|
+
}
|
|
2834
|
+
// fileId -> blobUrl
|
|
2835
|
+
/**
|
|
2836
|
+
* Creates a blob URL for a file and tracks it.
|
|
2837
|
+
*/
|
|
2838
|
+
createUrl(fileId, blob) {
|
|
2839
|
+
this.revokeUrl(fileId);
|
|
2840
|
+
const url = URL.createObjectURL(blob);
|
|
2841
|
+
this.activeUrls.set(fileId, url);
|
|
2842
|
+
return url;
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Gets the active blob URL for a file, if any.
|
|
2846
|
+
*/
|
|
2847
|
+
getUrl(fileId) {
|
|
2848
|
+
return this.activeUrls.get(fileId);
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Revokes a blob URL and removes it from tracking.
|
|
2852
|
+
*/
|
|
2853
|
+
revokeUrl(fileId) {
|
|
2854
|
+
const url = this.activeUrls.get(fileId);
|
|
2855
|
+
if (url) {
|
|
2856
|
+
URL.revokeObjectURL(url);
|
|
2857
|
+
this.activeUrls.delete(fileId);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Revokes all tracked blob URLs.
|
|
2862
|
+
*/
|
|
2863
|
+
revokeAll() {
|
|
2864
|
+
for (const url of this.activeUrls.values()) {
|
|
2865
|
+
URL.revokeObjectURL(url);
|
|
2866
|
+
}
|
|
2867
|
+
this.activeUrls.clear();
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Gets the count of active blob URLs.
|
|
2871
|
+
*/
|
|
2872
|
+
get size() {
|
|
2873
|
+
return this.activeUrls.size;
|
|
2874
|
+
}
|
|
2875
|
+
};
|
|
2876
|
+
|
|
2703
2877
|
// src/react/useChatStorage.ts
|
|
2704
2878
|
function replaceUrlWithMCPPlaceholder(content, url, fileId) {
|
|
2705
2879
|
const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -2721,20 +2895,69 @@ function replaceUrlWithMCPPlaceholder(content, url, fileId) {
|
|
|
2721
2895
|
function findFileIdBySourceUrl(files, sourceUrl) {
|
|
2722
2896
|
return files?.find((f) => f.sourceUrl === sourceUrl)?.id;
|
|
2723
2897
|
}
|
|
2724
|
-
function
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2898
|
+
async function isUrlValid(url, timeoutMs = 5e3) {
|
|
2899
|
+
try {
|
|
2900
|
+
const controller = new AbortController();
|
|
2901
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2902
|
+
const response = await fetch(url, {
|
|
2903
|
+
method: "GET",
|
|
2904
|
+
headers: { Range: "bytes=0-0" },
|
|
2905
|
+
signal: controller.signal
|
|
2906
|
+
});
|
|
2907
|
+
clearTimeout(timeoutId);
|
|
2908
|
+
return response.ok || response.status === 206;
|
|
2909
|
+
} catch {
|
|
2910
|
+
return false;
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
async function storedToLlmapiMessage(stored) {
|
|
2914
|
+
let textContent = stored.content;
|
|
2915
|
+
const fileUrlMap = /* @__PURE__ */ new Map();
|
|
2916
|
+
const imageParts = [];
|
|
2728
2917
|
if (stored.files?.length) {
|
|
2729
2918
|
for (const file of stored.files) {
|
|
2730
2919
|
if (file.url) {
|
|
2731
|
-
|
|
2920
|
+
imageParts.push({
|
|
2732
2921
|
type: "image_url",
|
|
2733
2922
|
image_url: { url: file.url }
|
|
2734
2923
|
});
|
|
2924
|
+
} else if (file.sourceUrl) {
|
|
2925
|
+
const isValid = await isUrlValid(file.sourceUrl);
|
|
2926
|
+
if (isValid) {
|
|
2927
|
+
imageParts.push({
|
|
2928
|
+
type: "image_url",
|
|
2929
|
+
image_url: { url: file.sourceUrl }
|
|
2930
|
+
});
|
|
2931
|
+
fileUrlMap.set(file.id, file.sourceUrl);
|
|
2932
|
+
}
|
|
2735
2933
|
}
|
|
2736
2934
|
}
|
|
2737
2935
|
}
|
|
2936
|
+
textContent = textContent.replace(
|
|
2937
|
+
/__SDKFILE__([a-f0-9-]+)__/g,
|
|
2938
|
+
(match, fileId) => {
|
|
2939
|
+
const sourceUrl = fileUrlMap.get(fileId);
|
|
2940
|
+
if (sourceUrl) {
|
|
2941
|
+
return ``;
|
|
2942
|
+
}
|
|
2943
|
+
return "";
|
|
2944
|
+
}
|
|
2945
|
+
);
|
|
2946
|
+
textContent = textContent.replace(
|
|
2947
|
+
/!\[MCP_IMAGE:([a-f0-9-]+)\]/g,
|
|
2948
|
+
(match, fileId) => {
|
|
2949
|
+
const sourceUrl = fileUrlMap.get(fileId);
|
|
2950
|
+
if (sourceUrl) {
|
|
2951
|
+
return ``;
|
|
2952
|
+
}
|
|
2953
|
+
return "";
|
|
2954
|
+
}
|
|
2955
|
+
);
|
|
2956
|
+
textContent = textContent.replace(/\n{3,}/g, "\n\n").trim();
|
|
2957
|
+
const content = [
|
|
2958
|
+
{ type: "text", text: textContent },
|
|
2959
|
+
...imageParts
|
|
2960
|
+
];
|
|
2738
2961
|
return {
|
|
2739
2962
|
role: stored.role,
|
|
2740
2963
|
content
|
|
@@ -2751,9 +2974,17 @@ function useChatStorage(options) {
|
|
|
2751
2974
|
onData,
|
|
2752
2975
|
onFinish,
|
|
2753
2976
|
onError,
|
|
2754
|
-
apiType
|
|
2977
|
+
apiType,
|
|
2978
|
+
walletAddress
|
|
2755
2979
|
} = options;
|
|
2756
2980
|
const [currentConversationId, setCurrentConversationId] = useState2(initialConversationId || null);
|
|
2981
|
+
const blobManagerRef = useRef2(new BlobUrlManager());
|
|
2982
|
+
useEffect2(() => {
|
|
2983
|
+
const manager = blobManagerRef.current;
|
|
2984
|
+
return () => {
|
|
2985
|
+
manager.revokeAll();
|
|
2986
|
+
};
|
|
2987
|
+
}, []);
|
|
2757
2988
|
const messagesCollection = useMemo(
|
|
2758
2989
|
() => database.get("history"),
|
|
2759
2990
|
[database]
|
|
@@ -2821,9 +3052,49 @@ function useChatStorage(options) {
|
|
|
2821
3052
|
);
|
|
2822
3053
|
const getMessages = useCallback2(
|
|
2823
3054
|
async (convId) => {
|
|
2824
|
-
|
|
3055
|
+
const messages = await getMessagesOp(storageCtx, convId);
|
|
3056
|
+
if (walletAddress && hasEncryptionKey(walletAddress) && isOPFSSupported()) {
|
|
3057
|
+
try {
|
|
3058
|
+
const encryptionKey = await getEncryptionKey(walletAddress);
|
|
3059
|
+
const blobManager = blobManagerRef.current;
|
|
3060
|
+
const resolvedMessages = await Promise.all(
|
|
3061
|
+
messages.map(async (msg) => {
|
|
3062
|
+
const fileIds = extractFileIds(msg.content);
|
|
3063
|
+
if (fileIds.length === 0) {
|
|
3064
|
+
return msg;
|
|
3065
|
+
}
|
|
3066
|
+
let resolvedContent = msg.content;
|
|
3067
|
+
for (const fileId of fileIds) {
|
|
3068
|
+
let url = blobManager.getUrl(fileId);
|
|
3069
|
+
if (!url) {
|
|
3070
|
+
const result = await readEncryptedFile(fileId, encryptionKey);
|
|
3071
|
+
if (result) {
|
|
3072
|
+
url = blobManager.createUrl(fileId, result.blob);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
if (url) {
|
|
3076
|
+
const placeholder = createFilePlaceholder(fileId);
|
|
3077
|
+
resolvedContent = resolvedContent.replace(
|
|
3078
|
+
new RegExp(
|
|
3079
|
+
placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
|
|
3080
|
+
"g"
|
|
3081
|
+
),
|
|
3082
|
+
``
|
|
3083
|
+
);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
return { ...msg, content: resolvedContent };
|
|
3087
|
+
})
|
|
3088
|
+
);
|
|
3089
|
+
return resolvedMessages;
|
|
3090
|
+
} catch (error) {
|
|
3091
|
+
console.error("[useChatStorage] Failed to resolve file placeholders:", error);
|
|
3092
|
+
return messages;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
return messages;
|
|
2825
3096
|
},
|
|
2826
|
-
[storageCtx]
|
|
3097
|
+
[storageCtx, walletAddress]
|
|
2827
3098
|
);
|
|
2828
3099
|
const getMessageCount = useCallback2(
|
|
2829
3100
|
async (convId) => {
|
|
@@ -3047,6 +3318,89 @@ function useChatStorage(options) {
|
|
|
3047
3318
|
},
|
|
3048
3319
|
[]
|
|
3049
3320
|
);
|
|
3321
|
+
const extractAndStoreEncryptedMCPImages = useCallback2(
|
|
3322
|
+
async (content, address) => {
|
|
3323
|
+
try {
|
|
3324
|
+
if (!isOPFSSupported()) {
|
|
3325
|
+
console.warn("[extractAndStoreEncryptedMCPImages] OPFS not supported");
|
|
3326
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3327
|
+
}
|
|
3328
|
+
if (!hasEncryptionKey(address)) {
|
|
3329
|
+
console.warn("[extractAndStoreEncryptedMCPImages] Encryption key not available");
|
|
3330
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3331
|
+
}
|
|
3332
|
+
const MCP_IMAGE_URL_PATTERN = new RegExp(
|
|
3333
|
+
`https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
|
|
3334
|
+
"g"
|
|
3335
|
+
);
|
|
3336
|
+
const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
|
|
3337
|
+
if (!urlMatches || urlMatches.length === 0) {
|
|
3338
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3339
|
+
}
|
|
3340
|
+
const uniqueUrls = [...new Set(urlMatches)];
|
|
3341
|
+
const encryptionKey = await getEncryptionKey(address);
|
|
3342
|
+
const processedFiles = [];
|
|
3343
|
+
let cleanedContent = content;
|
|
3344
|
+
const results = await Promise.allSettled(
|
|
3345
|
+
uniqueUrls.map(async (imageUrl) => {
|
|
3346
|
+
const controller = new AbortController();
|
|
3347
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
3348
|
+
try {
|
|
3349
|
+
const response = await fetch(imageUrl, {
|
|
3350
|
+
signal: controller.signal,
|
|
3351
|
+
cache: "no-store"
|
|
3352
|
+
});
|
|
3353
|
+
if (!response.ok) {
|
|
3354
|
+
throw new Error(`Failed to fetch image: ${response.status}`);
|
|
3355
|
+
}
|
|
3356
|
+
const blob = await response.blob();
|
|
3357
|
+
const fileId = crypto.randomUUID();
|
|
3358
|
+
const urlPath = imageUrl.split("?")[0] ?? imageUrl;
|
|
3359
|
+
const extension = urlPath.match(/\.([a-zA-Z0-9]+)$/)?.[1] || "png";
|
|
3360
|
+
const mimeType = blob.type || `image/${extension}`;
|
|
3361
|
+
const fileName = `mcp-image-${Date.now()}-${fileId.slice(0, 8)}.${extension}`;
|
|
3362
|
+
await writeEncryptedFile(fileId, blob, encryptionKey, {
|
|
3363
|
+
name: fileName,
|
|
3364
|
+
sourceUrl: imageUrl
|
|
3365
|
+
});
|
|
3366
|
+
return { fileId, fileName, mimeType, size: blob.size, imageUrl };
|
|
3367
|
+
} finally {
|
|
3368
|
+
clearTimeout(timeoutId);
|
|
3369
|
+
}
|
|
3370
|
+
})
|
|
3371
|
+
);
|
|
3372
|
+
results.forEach((result, i) => {
|
|
3373
|
+
const imageUrl = uniqueUrls[i];
|
|
3374
|
+
if (result.status === "fulfilled") {
|
|
3375
|
+
const { fileId, fileName, mimeType, size } = result.value;
|
|
3376
|
+
processedFiles.push({
|
|
3377
|
+
id: fileId,
|
|
3378
|
+
name: fileName,
|
|
3379
|
+
type: mimeType,
|
|
3380
|
+
size,
|
|
3381
|
+
sourceUrl: imageUrl
|
|
3382
|
+
});
|
|
3383
|
+
const placeholder = createFilePlaceholder(fileId);
|
|
3384
|
+
const escapedUrl = imageUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3385
|
+
const markdownImagePattern = new RegExp(
|
|
3386
|
+
`!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
|
|
3387
|
+
"g"
|
|
3388
|
+
);
|
|
3389
|
+
cleanedContent = cleanedContent.replace(markdownImagePattern, placeholder);
|
|
3390
|
+
cleanedContent = cleanedContent.replace(new RegExp(escapedUrl, "g"), placeholder);
|
|
3391
|
+
} else {
|
|
3392
|
+
console.error("[extractAndStoreEncryptedMCPImages] Failed:", result.reason);
|
|
3393
|
+
}
|
|
3394
|
+
});
|
|
3395
|
+
cleanedContent = cleanedContent.replace(/\n{3,}/g, "\n\n").trim();
|
|
3396
|
+
return { processedFiles, cleanedContent };
|
|
3397
|
+
} catch (err) {
|
|
3398
|
+
console.error("[extractAndStoreEncryptedMCPImages] Unexpected error:", err);
|
|
3399
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3400
|
+
}
|
|
3401
|
+
},
|
|
3402
|
+
[]
|
|
3403
|
+
);
|
|
3050
3404
|
const sendMessage = useCallback2(
|
|
3051
3405
|
async (args) => {
|
|
3052
3406
|
const {
|
|
@@ -3098,10 +3452,10 @@ function useChatStorage(options) {
|
|
|
3098
3452
|
const storedMessages = await getMessages(convId);
|
|
3099
3453
|
const validMessages = storedMessages.filter((msg) => !msg.error);
|
|
3100
3454
|
const limitedMessages = validMessages.slice(-maxHistoryMessages);
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
];
|
|
3455
|
+
const historyMessages = await Promise.all(
|
|
3456
|
+
limitedMessages.map(storedToLlmapiMessage)
|
|
3457
|
+
);
|
|
3458
|
+
messagesToSend = [...historyMessages, ...messages];
|
|
3105
3459
|
} else {
|
|
3106
3460
|
messagesToSend = [...messages];
|
|
3107
3461
|
}
|
|
@@ -3239,7 +3593,14 @@ function useChatStorage(options) {
|
|
|
3239
3593
|
let cleanedContent = assistantContent.replace(jsonSourcesBlockRegex, "").trim();
|
|
3240
3594
|
cleanedContent = cleanedContent.replace(/\n{3,}/g, "\n\n");
|
|
3241
3595
|
let processedFiles = [];
|
|
3242
|
-
if (
|
|
3596
|
+
if (walletAddress) {
|
|
3597
|
+
const result2 = await extractAndStoreEncryptedMCPImages(
|
|
3598
|
+
cleanedContent,
|
|
3599
|
+
walletAddress
|
|
3600
|
+
);
|
|
3601
|
+
processedFiles = result2.processedFiles;
|
|
3602
|
+
cleanedContent = result2.cleanedContent;
|
|
3603
|
+
} else if (writeFile) {
|
|
3243
3604
|
const result2 = await extractAndStoreMCPImages(
|
|
3244
3605
|
cleanedContent,
|
|
3245
3606
|
writeFile
|
|
@@ -3275,7 +3636,7 @@ function useChatStorage(options) {
|
|
|
3275
3636
|
assistantMessage: storedAssistantMessage
|
|
3276
3637
|
};
|
|
3277
3638
|
},
|
|
3278
|
-
[ensureConversation, getMessages, storageCtx, baseSendMessage]
|
|
3639
|
+
[ensureConversation, getMessages, storageCtx, baseSendMessage, walletAddress, extractAndStoreEncryptedMCPImages]
|
|
3279
3640
|
);
|
|
3280
3641
|
const searchMessages = useCallback2(
|
|
3281
3642
|
async (queryVector, options2) => {
|
|
@@ -3598,7 +3959,7 @@ var sdkModelClasses = [
|
|
|
3598
3959
|
];
|
|
3599
3960
|
|
|
3600
3961
|
// src/react/useMemoryStorage.ts
|
|
3601
|
-
import { useCallback as useCallback3, useState as useState3, useMemo as useMemo2, useRef as
|
|
3962
|
+
import { useCallback as useCallback3, useState as useState3, useMemo as useMemo2, useRef as useRef3 } from "react";
|
|
3602
3963
|
import { postApiV1ChatCompletions } from "@reverbia/sdk";
|
|
3603
3964
|
|
|
3604
3965
|
// src/lib/db/memory/schema.ts
|
|
@@ -4390,7 +4751,7 @@ function useMemoryStorage(options) {
|
|
|
4390
4751
|
} = options;
|
|
4391
4752
|
const embeddingModel = userEmbeddingModel === void 0 ? DEFAULT_API_EMBEDDING_MODEL : userEmbeddingModel;
|
|
4392
4753
|
const [memories, setMemories] = useState3([]);
|
|
4393
|
-
const extractionInProgressRef =
|
|
4754
|
+
const extractionInProgressRef = useRef3(false);
|
|
4394
4755
|
const memoriesCollection = useMemo2(
|
|
4395
4756
|
() => database.get("memories"),
|
|
4396
4757
|
[database]
|
|
@@ -4914,7 +5275,7 @@ function useMemoryStorage(options) {
|
|
|
4914
5275
|
|
|
4915
5276
|
// src/react/useSettings.ts
|
|
4916
5277
|
import { Q as Q5 } from "@nozbe/watermelondb";
|
|
4917
|
-
import { useCallback as useCallback4, useState as useState4, useMemo as useMemo3, useEffect as
|
|
5278
|
+
import { useCallback as useCallback4, useState as useState4, useMemo as useMemo3, useEffect as useEffect3 } from "react";
|
|
4918
5279
|
|
|
4919
5280
|
// src/lib/db/settings/schema.ts
|
|
4920
5281
|
import { appSchema as appSchema4, tableSchema as tableSchema4 } from "@nozbe/watermelondb";
|
|
@@ -5391,7 +5752,7 @@ function useSettings(options) {
|
|
|
5391
5752
|
},
|
|
5392
5753
|
[storageCtx, walletAddress]
|
|
5393
5754
|
);
|
|
5394
|
-
|
|
5755
|
+
useEffect3(() => {
|
|
5395
5756
|
if (!walletAddress) {
|
|
5396
5757
|
setModelPreferenceState(null);
|
|
5397
5758
|
setUserPreferenceState(null);
|
|
@@ -5429,7 +5790,7 @@ function useSettings(options) {
|
|
|
5429
5790
|
cancelled = true;
|
|
5430
5791
|
};
|
|
5431
5792
|
}, [walletAddress, storageCtx, legacyStorageCtx]);
|
|
5432
|
-
|
|
5793
|
+
useEffect3(() => {
|
|
5433
5794
|
if (!walletAddress) return;
|
|
5434
5795
|
const subscription = userPreferencesCollection.query(Q5.where("wallet_address", walletAddress)).observeWithColumns([
|
|
5435
5796
|
"nickname",
|
|
@@ -5670,22 +6031,22 @@ ${text5}`;
|
|
|
5670
6031
|
}
|
|
5671
6032
|
|
|
5672
6033
|
// src/react/useModels.ts
|
|
5673
|
-
import { useCallback as useCallback7, useEffect as
|
|
6034
|
+
import { useCallback as useCallback7, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
|
|
5674
6035
|
function useModels(options = {}) {
|
|
5675
6036
|
const { getToken, baseUrl = BASE_URL, provider, autoFetch = true } = options;
|
|
5676
6037
|
const [models, setModels] = useState7([]);
|
|
5677
6038
|
const [isLoading, setIsLoading] = useState7(false);
|
|
5678
6039
|
const [error, setError] = useState7(null);
|
|
5679
|
-
const getTokenRef =
|
|
5680
|
-
const baseUrlRef =
|
|
5681
|
-
const providerRef =
|
|
5682
|
-
const abortControllerRef =
|
|
5683
|
-
|
|
6040
|
+
const getTokenRef = useRef4(getToken);
|
|
6041
|
+
const baseUrlRef = useRef4(baseUrl);
|
|
6042
|
+
const providerRef = useRef4(provider);
|
|
6043
|
+
const abortControllerRef = useRef4(null);
|
|
6044
|
+
useEffect4(() => {
|
|
5684
6045
|
getTokenRef.current = getToken;
|
|
5685
6046
|
baseUrlRef.current = baseUrl;
|
|
5686
6047
|
providerRef.current = provider;
|
|
5687
6048
|
});
|
|
5688
|
-
|
|
6049
|
+
useEffect4(() => {
|
|
5689
6050
|
return () => {
|
|
5690
6051
|
if (abortControllerRef.current) {
|
|
5691
6052
|
abortControllerRef.current.abort();
|
|
@@ -5755,8 +6116,8 @@ function useModels(options = {}) {
|
|
|
5755
6116
|
setModels([]);
|
|
5756
6117
|
await fetchModels();
|
|
5757
6118
|
}, [fetchModels]);
|
|
5758
|
-
const hasFetchedRef =
|
|
5759
|
-
|
|
6119
|
+
const hasFetchedRef = useRef4(false);
|
|
6120
|
+
useEffect4(() => {
|
|
5760
6121
|
if (autoFetch && !hasFetchedRef.current) {
|
|
5761
6122
|
hasFetchedRef.current = true;
|
|
5762
6123
|
fetchModels();
|
|
@@ -5774,15 +6135,15 @@ function useModels(options = {}) {
|
|
|
5774
6135
|
}
|
|
5775
6136
|
|
|
5776
6137
|
// src/react/useSearch.ts
|
|
5777
|
-
import { useCallback as useCallback8, useEffect as
|
|
6138
|
+
import { useCallback as useCallback8, useEffect as useEffect5, useRef as useRef5, useState as useState8 } from "react";
|
|
5778
6139
|
function useSearch(options = {}) {
|
|
5779
6140
|
const { getToken, baseUrl = BASE_URL, onError } = options;
|
|
5780
6141
|
const [isLoading, setIsLoading] = useState8(false);
|
|
5781
6142
|
const [results, setResults] = useState8(null);
|
|
5782
6143
|
const [response, setResponse] = useState8(null);
|
|
5783
6144
|
const [error, setError] = useState8(null);
|
|
5784
|
-
const abortControllerRef =
|
|
5785
|
-
|
|
6145
|
+
const abortControllerRef = useRef5(null);
|
|
6146
|
+
useEffect5(() => {
|
|
5786
6147
|
return () => {
|
|
5787
6148
|
if (abortControllerRef.current) {
|
|
5788
6149
|
abortControllerRef.current.abort();
|
|
@@ -5858,12 +6219,12 @@ function useSearch(options = {}) {
|
|
|
5858
6219
|
}
|
|
5859
6220
|
|
|
5860
6221
|
// src/react/useImageGeneration.ts
|
|
5861
|
-
import { useCallback as useCallback9, useEffect as
|
|
6222
|
+
import { useCallback as useCallback9, useEffect as useEffect6, useRef as useRef6, useState as useState9 } from "react";
|
|
5862
6223
|
function useImageGeneration(options = {}) {
|
|
5863
6224
|
const { getToken, baseUrl = BASE_URL, onFinish, onError } = options;
|
|
5864
6225
|
const [isLoading, setIsLoading] = useState9(false);
|
|
5865
|
-
const abortControllerRef =
|
|
5866
|
-
|
|
6226
|
+
const abortControllerRef = useRef6(null);
|
|
6227
|
+
useEffect6(() => {
|
|
5867
6228
|
return () => {
|
|
5868
6229
|
if (abortControllerRef.current) {
|
|
5869
6230
|
abortControllerRef.current.abort();
|
|
@@ -6245,7 +6606,7 @@ import {
|
|
|
6245
6606
|
createElement,
|
|
6246
6607
|
useCallback as useCallback10,
|
|
6247
6608
|
useContext,
|
|
6248
|
-
useEffect as
|
|
6609
|
+
useEffect as useEffect7,
|
|
6249
6610
|
useState as useState10
|
|
6250
6611
|
} from "react";
|
|
6251
6612
|
|
|
@@ -6576,7 +6937,7 @@ function DropboxAuthProvider({
|
|
|
6576
6937
|
}) {
|
|
6577
6938
|
const [accessToken, setAccessToken] = useState10(null);
|
|
6578
6939
|
const isConfigured = !!appKey;
|
|
6579
|
-
|
|
6940
|
+
useEffect7(() => {
|
|
6580
6941
|
const checkStoredToken = async () => {
|
|
6581
6942
|
if (walletAddress) {
|
|
6582
6943
|
await migrateUnencryptedTokens("dropbox", walletAddress);
|
|
@@ -6590,7 +6951,7 @@ function DropboxAuthProvider({
|
|
|
6590
6951
|
};
|
|
6591
6952
|
checkStoredToken();
|
|
6592
6953
|
}, [apiClient, walletAddress]);
|
|
6593
|
-
|
|
6954
|
+
useEffect7(() => {
|
|
6594
6955
|
if (!isConfigured) return;
|
|
6595
6956
|
const handleCallback = async () => {
|
|
6596
6957
|
if (isDropboxCallback()) {
|
|
@@ -6762,7 +7123,7 @@ import {
|
|
|
6762
7123
|
createElement as createElement2,
|
|
6763
7124
|
useCallback as useCallback12,
|
|
6764
7125
|
useContext as useContext2,
|
|
6765
|
-
useEffect as
|
|
7126
|
+
useEffect as useEffect8,
|
|
6766
7127
|
useState as useState11
|
|
6767
7128
|
} from "react";
|
|
6768
7129
|
|
|
@@ -6996,7 +7357,7 @@ function GoogleDriveAuthProvider({
|
|
|
6996
7357
|
}) {
|
|
6997
7358
|
const [accessToken, setAccessToken] = useState11(null);
|
|
6998
7359
|
const isConfigured = !!clientId;
|
|
6999
|
-
|
|
7360
|
+
useEffect8(() => {
|
|
7000
7361
|
const checkStoredToken = async () => {
|
|
7001
7362
|
if (walletAddress) {
|
|
7002
7363
|
await migrateUnencryptedTokens("google-drive", walletAddress);
|
|
@@ -7010,7 +7371,7 @@ function GoogleDriveAuthProvider({
|
|
|
7010
7371
|
};
|
|
7011
7372
|
checkStoredToken();
|
|
7012
7373
|
}, [apiClient, walletAddress]);
|
|
7013
|
-
|
|
7374
|
+
useEffect8(() => {
|
|
7014
7375
|
if (!isConfigured) return;
|
|
7015
7376
|
const handleCallback = async () => {
|
|
7016
7377
|
if (isGoogleDriveCallback()) {
|
|
@@ -7493,7 +7854,7 @@ import {
|
|
|
7493
7854
|
createElement as createElement3,
|
|
7494
7855
|
useCallback as useCallback14,
|
|
7495
7856
|
useContext as useContext3,
|
|
7496
|
-
useEffect as
|
|
7857
|
+
useEffect as useEffect9,
|
|
7497
7858
|
useState as useState12
|
|
7498
7859
|
} from "react";
|
|
7499
7860
|
|
|
@@ -7743,7 +8104,7 @@ function ICloudAuthProvider({
|
|
|
7743
8104
|
const [isAvailable, setIsAvailable] = useState12(false);
|
|
7744
8105
|
const [isConfigured, setIsConfigured] = useState12(false);
|
|
7745
8106
|
const [isLoading, setIsLoading] = useState12(false);
|
|
7746
|
-
|
|
8107
|
+
useEffect9(() => {
|
|
7747
8108
|
if (!apiToken || typeof window === "undefined") {
|
|
7748
8109
|
return;
|
|
7749
8110
|
}
|
|
@@ -8059,7 +8420,7 @@ import {
|
|
|
8059
8420
|
createElement as createElement4,
|
|
8060
8421
|
useCallback as useCallback16,
|
|
8061
8422
|
useContext as useContext4,
|
|
8062
|
-
useEffect as
|
|
8423
|
+
useEffect as useEffect10,
|
|
8063
8424
|
useState as useState13
|
|
8064
8425
|
} from "react";
|
|
8065
8426
|
var BackupAuthContext = createContext4(null);
|
|
@@ -8083,7 +8444,7 @@ function BackupAuthProvider({
|
|
|
8083
8444
|
const [icloudUserRecordName, setIcloudUserRecordName] = useState13(null);
|
|
8084
8445
|
const [isIcloudAvailable, setIsIcloudAvailable] = useState13(false);
|
|
8085
8446
|
const isIcloudConfigured = isIcloudAvailable && !!icloudApiToken;
|
|
8086
|
-
|
|
8447
|
+
useEffect10(() => {
|
|
8087
8448
|
const checkStoredTokens = async () => {
|
|
8088
8449
|
if (walletAddress) {
|
|
8089
8450
|
await Promise.all([
|
|
@@ -8106,7 +8467,7 @@ function BackupAuthProvider({
|
|
|
8106
8467
|
};
|
|
8107
8468
|
checkStoredTokens();
|
|
8108
8469
|
}, [apiClient, walletAddress]);
|
|
8109
|
-
|
|
8470
|
+
useEffect10(() => {
|
|
8110
8471
|
if (!icloudApiToken || typeof window === "undefined") {
|
|
8111
8472
|
return;
|
|
8112
8473
|
}
|
|
@@ -8134,7 +8495,7 @@ function BackupAuthProvider({
|
|
|
8134
8495
|
};
|
|
8135
8496
|
initCloudKit();
|
|
8136
8497
|
}, [icloudApiToken, icloudContainerIdentifier, icloudEnvironment]);
|
|
8137
|
-
|
|
8498
|
+
useEffect10(() => {
|
|
8138
8499
|
if (!isDropboxConfigured) return;
|
|
8139
8500
|
const handleCallback = async () => {
|
|
8140
8501
|
if (isDropboxCallback()) {
|
|
@@ -8154,7 +8515,7 @@ function BackupAuthProvider({
|
|
|
8154
8515
|
};
|
|
8155
8516
|
handleCallback();
|
|
8156
8517
|
}, [dropboxCallbackPath, isDropboxConfigured, apiClient, walletAddress]);
|
|
8157
|
-
|
|
8518
|
+
useEffect10(() => {
|
|
8158
8519
|
if (!isGoogleConfigured) return;
|
|
8159
8520
|
const handleCallback = async () => {
|
|
8160
8521
|
if (isGoogleDriveCallback()) {
|
|
@@ -9082,6 +9443,7 @@ export {
|
|
|
9082
9443
|
DEFAULT_ROOT_FOLDER as BACKUP_DRIVE_ROOT_FOLDER,
|
|
9083
9444
|
DEFAULT_BACKUP_FOLDER2 as BACKUP_ICLOUD_FOLDER,
|
|
9084
9445
|
BackupAuthProvider,
|
|
9446
|
+
BlobUrlManager,
|
|
9085
9447
|
Conversation as ChatConversation,
|
|
9086
9448
|
Message as ChatMessage,
|
|
9087
9449
|
DEFAULT_BACKUP_FOLDER,
|
|
@@ -9111,9 +9473,11 @@ export {
|
|
|
9111
9473
|
createMemoryContextSystemMessage,
|
|
9112
9474
|
decryptData,
|
|
9113
9475
|
decryptDataBytes,
|
|
9476
|
+
deleteEncryptedFile,
|
|
9114
9477
|
encryptData,
|
|
9115
9478
|
exportPublicKey,
|
|
9116
9479
|
extractConversationContext,
|
|
9480
|
+
fileExists,
|
|
9117
9481
|
findFileIdBySourceUrl,
|
|
9118
9482
|
formatMemoriesForChat,
|
|
9119
9483
|
generateCompositeKey,
|
|
@@ -9125,6 +9489,7 @@ export {
|
|
|
9125
9489
|
getAndClearDriveReturnUrl,
|
|
9126
9490
|
getCalendarAccessToken,
|
|
9127
9491
|
getDriveAccessToken,
|
|
9492
|
+
getEncryptionKey,
|
|
9128
9493
|
getGoogleDriveStoredToken,
|
|
9129
9494
|
getValidCalendarToken,
|
|
9130
9495
|
getValidDriveToken,
|
|
@@ -9139,7 +9504,9 @@ export {
|
|
|
9139
9504
|
hasKeyPair,
|
|
9140
9505
|
isCalendarCallback,
|
|
9141
9506
|
isDriveCallback,
|
|
9507
|
+
isOPFSSupported,
|
|
9142
9508
|
memoryStorageSchema,
|
|
9509
|
+
readEncryptedFile,
|
|
9143
9510
|
refreshCalendarToken,
|
|
9144
9511
|
refreshDriveToken,
|
|
9145
9512
|
replaceUrlWithMCPPlaceholder,
|
|
@@ -9177,5 +9544,6 @@ export {
|
|
|
9177
9544
|
usePdf,
|
|
9178
9545
|
useSearch,
|
|
9179
9546
|
useSettings,
|
|
9180
|
-
userPreferencesStorageSchema
|
|
9547
|
+
userPreferencesStorageSchema,
|
|
9548
|
+
writeEncryptedFile
|
|
9181
9549
|
};
|