@reverbia/sdk 1.0.0-next.20260110032702 → 1.0.0-next.20260110210906
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.d.mts +26 -0
- package/dist/expo/index.d.ts +26 -0
- package/dist/react/index.cjs +418 -19
- package/dist/react/index.d.mts +159 -1
- package/dist/react/index.d.ts +159 -1
- package/dist/react/index.mjs +442 -52
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -2255,7 +2255,7 @@ function useEncryption(signMessage, embeddedWalletSigner) {
|
|
|
2255
2255
|
}
|
|
2256
2256
|
|
|
2257
2257
|
// src/react/useChatStorage.ts
|
|
2258
|
-
import { useCallback as useCallback2, useState as useState2, useMemo } from "react";
|
|
2258
|
+
import { useCallback as useCallback2, useState as useState2, useMemo, useRef as useRef2, useEffect as useEffect2 } from "react";
|
|
2259
2259
|
|
|
2260
2260
|
// src/lib/db/chat/schema.ts
|
|
2261
2261
|
import { appSchema, tableSchema } from "@nozbe/watermelondb";
|
|
@@ -2700,21 +2700,262 @@ async function searchMessagesOp(ctx, queryVector, options) {
|
|
|
2700
2700
|
return resultsWithSimilarity.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
2701
2701
|
}
|
|
2702
2702
|
|
|
2703
|
+
// src/lib/storage/opfs.ts
|
|
2704
|
+
var FILE_PLACEHOLDER_PREFIX = "__SDKFILE__";
|
|
2705
|
+
var FILE_PLACEHOLDER_SUFFIX = "__";
|
|
2706
|
+
var FILE_PLACEHOLDER_REGEX = /__SDKFILE__([a-f0-9-]+)__/g;
|
|
2707
|
+
function createFilePlaceholder(fileId) {
|
|
2708
|
+
return `${FILE_PLACEHOLDER_PREFIX}${fileId}${FILE_PLACEHOLDER_SUFFIX}`;
|
|
2709
|
+
}
|
|
2710
|
+
function extractFileIds(content) {
|
|
2711
|
+
const matches = content.matchAll(FILE_PLACEHOLDER_REGEX);
|
|
2712
|
+
return Array.from(matches, (m) => m[1]);
|
|
2713
|
+
}
|
|
2714
|
+
function isOPFSSupported() {
|
|
2715
|
+
return typeof navigator !== "undefined" && "storage" in navigator && "getDirectory" in navigator.storage;
|
|
2716
|
+
}
|
|
2717
|
+
async function getSDKDirectory() {
|
|
2718
|
+
const root = await navigator.storage.getDirectory();
|
|
2719
|
+
return root.getDirectoryHandle("reverbia-sdk-files", { create: true });
|
|
2720
|
+
}
|
|
2721
|
+
function bytesToHex2(bytes) {
|
|
2722
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2723
|
+
}
|
|
2724
|
+
function hexToBytes2(hex) {
|
|
2725
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
2726
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
2727
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
2728
|
+
}
|
|
2729
|
+
return bytes;
|
|
2730
|
+
}
|
|
2731
|
+
async function encryptBlob(blob, encryptionKey) {
|
|
2732
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
2733
|
+
const plaintext = new Uint8Array(arrayBuffer);
|
|
2734
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
2735
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
2736
|
+
{ name: "AES-GCM", iv },
|
|
2737
|
+
encryptionKey,
|
|
2738
|
+
plaintext
|
|
2739
|
+
);
|
|
2740
|
+
const combined = new Uint8Array(iv.length + ciphertext.byteLength);
|
|
2741
|
+
combined.set(iv, 0);
|
|
2742
|
+
combined.set(new Uint8Array(ciphertext), iv.length);
|
|
2743
|
+
return bytesToHex2(combined);
|
|
2744
|
+
}
|
|
2745
|
+
async function decryptToBytes(encryptedHex, encryptionKey) {
|
|
2746
|
+
const combined = hexToBytes2(encryptedHex);
|
|
2747
|
+
const iv = combined.slice(0, 12);
|
|
2748
|
+
const ciphertext = combined.slice(12);
|
|
2749
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
2750
|
+
{ name: "AES-GCM", iv },
|
|
2751
|
+
encryptionKey,
|
|
2752
|
+
ciphertext
|
|
2753
|
+
);
|
|
2754
|
+
return new Uint8Array(decrypted);
|
|
2755
|
+
}
|
|
2756
|
+
async function writeEncryptedFile(fileId, blob, encryptionKey, metadata) {
|
|
2757
|
+
if (!isOPFSSupported()) {
|
|
2758
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2759
|
+
}
|
|
2760
|
+
const dir = await getSDKDirectory();
|
|
2761
|
+
const encryptedHex = await encryptBlob(blob, encryptionKey);
|
|
2762
|
+
const contentHandle = await dir.getFileHandle(`${fileId}.enc`, {
|
|
2763
|
+
create: true
|
|
2764
|
+
});
|
|
2765
|
+
const contentWritable = await contentHandle.createWritable();
|
|
2766
|
+
await contentWritable.write(encryptedHex);
|
|
2767
|
+
await contentWritable.close();
|
|
2768
|
+
const fileMetadata = {
|
|
2769
|
+
id: fileId,
|
|
2770
|
+
name: metadata?.name || `file-${fileId}`,
|
|
2771
|
+
type: blob.type || "application/octet-stream",
|
|
2772
|
+
size: blob.size,
|
|
2773
|
+
sourceUrl: metadata?.sourceUrl,
|
|
2774
|
+
createdAt: Date.now()
|
|
2775
|
+
};
|
|
2776
|
+
const metaHandle = await dir.getFileHandle(`${fileId}.meta.json`, {
|
|
2777
|
+
create: true
|
|
2778
|
+
});
|
|
2779
|
+
const metaWritable = await metaHandle.createWritable();
|
|
2780
|
+
await metaWritable.write(JSON.stringify(fileMetadata));
|
|
2781
|
+
await metaWritable.close();
|
|
2782
|
+
}
|
|
2783
|
+
async function readEncryptedFile(fileId, encryptionKey) {
|
|
2784
|
+
if (!isOPFSSupported()) {
|
|
2785
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2786
|
+
}
|
|
2787
|
+
const dir = await getSDKDirectory();
|
|
2788
|
+
try {
|
|
2789
|
+
const contentHandle = await dir.getFileHandle(`${fileId}.enc`);
|
|
2790
|
+
const contentFile = await contentHandle.getFile();
|
|
2791
|
+
const encryptedHex = await contentFile.text();
|
|
2792
|
+
const metaHandle = await dir.getFileHandle(`${fileId}.meta.json`);
|
|
2793
|
+
const metaFile = await metaHandle.getFile();
|
|
2794
|
+
const metadata = JSON.parse(await metaFile.text());
|
|
2795
|
+
const decryptedBytes = await decryptToBytes(encryptedHex, encryptionKey);
|
|
2796
|
+
const blob = new Blob([decryptedBytes.buffer], { type: metadata.type });
|
|
2797
|
+
return { blob, metadata };
|
|
2798
|
+
} catch (error) {
|
|
2799
|
+
if (error instanceof DOMException && error.name === "NotFoundError") {
|
|
2800
|
+
return null;
|
|
2801
|
+
}
|
|
2802
|
+
throw error;
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
async function deleteEncryptedFile(fileId) {
|
|
2806
|
+
if (!isOPFSSupported()) {
|
|
2807
|
+
throw new Error("OPFS is not supported in this browser");
|
|
2808
|
+
}
|
|
2809
|
+
const dir = await getSDKDirectory();
|
|
2810
|
+
try {
|
|
2811
|
+
await dir.removeEntry(`${fileId}.enc`);
|
|
2812
|
+
await dir.removeEntry(`${fileId}.meta.json`);
|
|
2813
|
+
} catch {
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
async function fileExists(fileId) {
|
|
2817
|
+
if (!isOPFSSupported()) {
|
|
2818
|
+
return false;
|
|
2819
|
+
}
|
|
2820
|
+
const dir = await getSDKDirectory();
|
|
2821
|
+
try {
|
|
2822
|
+
await dir.getFileHandle(`${fileId}.enc`);
|
|
2823
|
+
return true;
|
|
2824
|
+
} catch {
|
|
2825
|
+
return false;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
var BlobUrlManager = class {
|
|
2829
|
+
constructor() {
|
|
2830
|
+
this.activeUrls = /* @__PURE__ */ new Map();
|
|
2831
|
+
}
|
|
2832
|
+
// fileId -> blobUrl
|
|
2833
|
+
/**
|
|
2834
|
+
* Creates a blob URL for a file and tracks it.
|
|
2835
|
+
*/
|
|
2836
|
+
createUrl(fileId, blob) {
|
|
2837
|
+
this.revokeUrl(fileId);
|
|
2838
|
+
const url = URL.createObjectURL(blob);
|
|
2839
|
+
this.activeUrls.set(fileId, url);
|
|
2840
|
+
return url;
|
|
2841
|
+
}
|
|
2842
|
+
/**
|
|
2843
|
+
* Gets the active blob URL for a file, if any.
|
|
2844
|
+
*/
|
|
2845
|
+
getUrl(fileId) {
|
|
2846
|
+
return this.activeUrls.get(fileId);
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Revokes a blob URL and removes it from tracking.
|
|
2850
|
+
*/
|
|
2851
|
+
revokeUrl(fileId) {
|
|
2852
|
+
const url = this.activeUrls.get(fileId);
|
|
2853
|
+
if (url) {
|
|
2854
|
+
URL.revokeObjectURL(url);
|
|
2855
|
+
this.activeUrls.delete(fileId);
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Revokes all tracked blob URLs.
|
|
2860
|
+
*/
|
|
2861
|
+
revokeAll() {
|
|
2862
|
+
for (const url of this.activeUrls.values()) {
|
|
2863
|
+
URL.revokeObjectURL(url);
|
|
2864
|
+
}
|
|
2865
|
+
this.activeUrls.clear();
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Gets the count of active blob URLs.
|
|
2869
|
+
*/
|
|
2870
|
+
get size() {
|
|
2871
|
+
return this.activeUrls.size;
|
|
2872
|
+
}
|
|
2873
|
+
};
|
|
2874
|
+
|
|
2703
2875
|
// src/react/useChatStorage.ts
|
|
2704
|
-
function
|
|
2705
|
-
const
|
|
2706
|
-
|
|
2707
|
-
|
|
2876
|
+
function replaceUrlWithMCPPlaceholder(content, url, fileId) {
|
|
2877
|
+
const escapedUrl = url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2878
|
+
const placeholder = `![MCP_IMAGE:${fileId}]`;
|
|
2879
|
+
let result = content;
|
|
2880
|
+
const markdownImagePattern = new RegExp(
|
|
2881
|
+
`!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
|
|
2882
|
+
"g"
|
|
2883
|
+
);
|
|
2884
|
+
result = result.replace(markdownImagePattern, placeholder);
|
|
2885
|
+
result = result.replace(new RegExp(escapedUrl, "g"), placeholder);
|
|
2886
|
+
const orphanedMarkdownPattern = new RegExp(
|
|
2887
|
+
`!\\[[^\\]]*\\]\\([\\s]*\\!\\[MCP_IMAGE:${fileId}\\][\\s]*\\)`,
|
|
2888
|
+
"g"
|
|
2889
|
+
);
|
|
2890
|
+
result = result.replace(orphanedMarkdownPattern, placeholder);
|
|
2891
|
+
return result;
|
|
2892
|
+
}
|
|
2893
|
+
function findFileIdBySourceUrl(files, sourceUrl) {
|
|
2894
|
+
return files?.find((f) => f.sourceUrl === sourceUrl)?.id;
|
|
2895
|
+
}
|
|
2896
|
+
async function isUrlValid(url, timeoutMs = 5e3) {
|
|
2897
|
+
try {
|
|
2898
|
+
const controller = new AbortController();
|
|
2899
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2900
|
+
const response = await fetch(url, {
|
|
2901
|
+
method: "GET",
|
|
2902
|
+
headers: { Range: "bytes=0-0" },
|
|
2903
|
+
signal: controller.signal
|
|
2904
|
+
});
|
|
2905
|
+
clearTimeout(timeoutId);
|
|
2906
|
+
return response.ok || response.status === 206;
|
|
2907
|
+
} catch {
|
|
2908
|
+
return false;
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
async function storedToLlmapiMessage(stored) {
|
|
2912
|
+
let textContent = stored.content;
|
|
2913
|
+
const fileUrlMap = /* @__PURE__ */ new Map();
|
|
2914
|
+
const imageParts = [];
|
|
2708
2915
|
if (stored.files?.length) {
|
|
2709
2916
|
for (const file of stored.files) {
|
|
2710
2917
|
if (file.url) {
|
|
2711
|
-
|
|
2918
|
+
imageParts.push({
|
|
2712
2919
|
type: "image_url",
|
|
2713
2920
|
image_url: { url: file.url }
|
|
2714
2921
|
});
|
|
2922
|
+
} else if (file.sourceUrl) {
|
|
2923
|
+
const isValid = await isUrlValid(file.sourceUrl);
|
|
2924
|
+
if (isValid) {
|
|
2925
|
+
imageParts.push({
|
|
2926
|
+
type: "image_url",
|
|
2927
|
+
image_url: { url: file.sourceUrl }
|
|
2928
|
+
});
|
|
2929
|
+
fileUrlMap.set(file.id, file.sourceUrl);
|
|
2930
|
+
}
|
|
2715
2931
|
}
|
|
2716
2932
|
}
|
|
2717
2933
|
}
|
|
2934
|
+
textContent = textContent.replace(
|
|
2935
|
+
/__SDKFILE__([a-f0-9-]+)__/g,
|
|
2936
|
+
(match, fileId) => {
|
|
2937
|
+
const sourceUrl = fileUrlMap.get(fileId);
|
|
2938
|
+
if (sourceUrl) {
|
|
2939
|
+
return ``;
|
|
2940
|
+
}
|
|
2941
|
+
return "";
|
|
2942
|
+
}
|
|
2943
|
+
);
|
|
2944
|
+
textContent = textContent.replace(
|
|
2945
|
+
/!\[MCP_IMAGE:([a-f0-9-]+)\]/g,
|
|
2946
|
+
(match, fileId) => {
|
|
2947
|
+
const sourceUrl = fileUrlMap.get(fileId);
|
|
2948
|
+
if (sourceUrl) {
|
|
2949
|
+
return ``;
|
|
2950
|
+
}
|
|
2951
|
+
return "";
|
|
2952
|
+
}
|
|
2953
|
+
);
|
|
2954
|
+
textContent = textContent.replace(/\n{3,}/g, "\n\n").trim();
|
|
2955
|
+
const content = [
|
|
2956
|
+
{ type: "text", text: textContent },
|
|
2957
|
+
...imageParts
|
|
2958
|
+
];
|
|
2718
2959
|
return {
|
|
2719
2960
|
role: stored.role,
|
|
2720
2961
|
content
|
|
@@ -2731,9 +2972,17 @@ function useChatStorage(options) {
|
|
|
2731
2972
|
onData,
|
|
2732
2973
|
onFinish,
|
|
2733
2974
|
onError,
|
|
2734
|
-
apiType
|
|
2975
|
+
apiType,
|
|
2976
|
+
walletAddress
|
|
2735
2977
|
} = options;
|
|
2736
2978
|
const [currentConversationId, setCurrentConversationId] = useState2(initialConversationId || null);
|
|
2979
|
+
const blobManagerRef = useRef2(new BlobUrlManager());
|
|
2980
|
+
useEffect2(() => {
|
|
2981
|
+
const manager = blobManagerRef.current;
|
|
2982
|
+
return () => {
|
|
2983
|
+
manager.revokeAll();
|
|
2984
|
+
};
|
|
2985
|
+
}, []);
|
|
2737
2986
|
const messagesCollection = useMemo(
|
|
2738
2987
|
() => database.get("history"),
|
|
2739
2988
|
[database]
|
|
@@ -2801,9 +3050,49 @@ function useChatStorage(options) {
|
|
|
2801
3050
|
);
|
|
2802
3051
|
const getMessages = useCallback2(
|
|
2803
3052
|
async (convId) => {
|
|
2804
|
-
|
|
3053
|
+
const messages = await getMessagesOp(storageCtx, convId);
|
|
3054
|
+
if (walletAddress && hasEncryptionKey(walletAddress) && isOPFSSupported()) {
|
|
3055
|
+
try {
|
|
3056
|
+
const encryptionKey = await getEncryptionKey(walletAddress);
|
|
3057
|
+
const blobManager = blobManagerRef.current;
|
|
3058
|
+
const resolvedMessages = await Promise.all(
|
|
3059
|
+
messages.map(async (msg) => {
|
|
3060
|
+
const fileIds = extractFileIds(msg.content);
|
|
3061
|
+
if (fileIds.length === 0) {
|
|
3062
|
+
return msg;
|
|
3063
|
+
}
|
|
3064
|
+
let resolvedContent = msg.content;
|
|
3065
|
+
for (const fileId of fileIds) {
|
|
3066
|
+
let url = blobManager.getUrl(fileId);
|
|
3067
|
+
if (!url) {
|
|
3068
|
+
const result = await readEncryptedFile(fileId, encryptionKey);
|
|
3069
|
+
if (result) {
|
|
3070
|
+
url = blobManager.createUrl(fileId, result.blob);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
if (url) {
|
|
3074
|
+
const placeholder = createFilePlaceholder(fileId);
|
|
3075
|
+
resolvedContent = resolvedContent.replace(
|
|
3076
|
+
new RegExp(
|
|
3077
|
+
placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
|
|
3078
|
+
"g"
|
|
3079
|
+
),
|
|
3080
|
+
``
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
return { ...msg, content: resolvedContent };
|
|
3085
|
+
})
|
|
3086
|
+
);
|
|
3087
|
+
return resolvedMessages;
|
|
3088
|
+
} catch (error) {
|
|
3089
|
+
console.error("[useChatStorage] Failed to resolve file placeholders:", error);
|
|
3090
|
+
return messages;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
return messages;
|
|
2805
3094
|
},
|
|
2806
|
-
[storageCtx]
|
|
3095
|
+
[storageCtx, walletAddress]
|
|
2807
3096
|
);
|
|
2808
3097
|
const getMessageCount = useCallback2(
|
|
2809
3098
|
async (convId) => {
|
|
@@ -2927,7 +3216,8 @@ function useChatStorage(options) {
|
|
|
2927
3216
|
[]
|
|
2928
3217
|
);
|
|
2929
3218
|
const extractAndStoreMCPImages = useCallback2(
|
|
2930
|
-
async (content, writeFile) => {
|
|
3219
|
+
async (content, writeFile, options2) => {
|
|
3220
|
+
const { replaceUrls = true } = options2 ?? {};
|
|
2931
3221
|
try {
|
|
2932
3222
|
const MCP_IMAGE_URL_PATTERN = new RegExp(
|
|
2933
3223
|
`https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
|
|
@@ -3001,9 +3291,10 @@ function useChatStorage(options) {
|
|
|
3001
3291
|
id: fileId,
|
|
3002
3292
|
name: fileName,
|
|
3003
3293
|
type: mimeType,
|
|
3004
|
-
size
|
|
3294
|
+
size,
|
|
3295
|
+
sourceUrl: imageUrl
|
|
3005
3296
|
});
|
|
3006
|
-
if (imageUrl) {
|
|
3297
|
+
if (replaceUrls && imageUrl) {
|
|
3007
3298
|
replaceUrlWithPlaceholder(imageUrl, fileId);
|
|
3008
3299
|
}
|
|
3009
3300
|
} else {
|
|
@@ -3025,6 +3316,89 @@ function useChatStorage(options) {
|
|
|
3025
3316
|
},
|
|
3026
3317
|
[]
|
|
3027
3318
|
);
|
|
3319
|
+
const extractAndStoreEncryptedMCPImages = useCallback2(
|
|
3320
|
+
async (content, address) => {
|
|
3321
|
+
try {
|
|
3322
|
+
if (!isOPFSSupported()) {
|
|
3323
|
+
console.warn("[extractAndStoreEncryptedMCPImages] OPFS not supported");
|
|
3324
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3325
|
+
}
|
|
3326
|
+
if (!hasEncryptionKey(address)) {
|
|
3327
|
+
console.warn("[extractAndStoreEncryptedMCPImages] Encryption key not available");
|
|
3328
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3329
|
+
}
|
|
3330
|
+
const MCP_IMAGE_URL_PATTERN = new RegExp(
|
|
3331
|
+
`https://${MCP_R2_DOMAIN.replace(/\./g, "\\.")}[^\\s)]*`,
|
|
3332
|
+
"g"
|
|
3333
|
+
);
|
|
3334
|
+
const urlMatches = content.match(MCP_IMAGE_URL_PATTERN);
|
|
3335
|
+
if (!urlMatches || urlMatches.length === 0) {
|
|
3336
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3337
|
+
}
|
|
3338
|
+
const uniqueUrls = [...new Set(urlMatches)];
|
|
3339
|
+
const encryptionKey = await getEncryptionKey(address);
|
|
3340
|
+
const processedFiles = [];
|
|
3341
|
+
let cleanedContent = content;
|
|
3342
|
+
const results = await Promise.allSettled(
|
|
3343
|
+
uniqueUrls.map(async (imageUrl) => {
|
|
3344
|
+
const controller = new AbortController();
|
|
3345
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
3346
|
+
try {
|
|
3347
|
+
const response = await fetch(imageUrl, {
|
|
3348
|
+
signal: controller.signal,
|
|
3349
|
+
cache: "no-store"
|
|
3350
|
+
});
|
|
3351
|
+
if (!response.ok) {
|
|
3352
|
+
throw new Error(`Failed to fetch image: ${response.status}`);
|
|
3353
|
+
}
|
|
3354
|
+
const blob = await response.blob();
|
|
3355
|
+
const fileId = crypto.randomUUID();
|
|
3356
|
+
const urlPath = imageUrl.split("?")[0] ?? imageUrl;
|
|
3357
|
+
const extension = urlPath.match(/\.([a-zA-Z0-9]+)$/)?.[1] || "png";
|
|
3358
|
+
const mimeType = blob.type || `image/${extension}`;
|
|
3359
|
+
const fileName = `mcp-image-${Date.now()}-${fileId.slice(0, 8)}.${extension}`;
|
|
3360
|
+
await writeEncryptedFile(fileId, blob, encryptionKey, {
|
|
3361
|
+
name: fileName,
|
|
3362
|
+
sourceUrl: imageUrl
|
|
3363
|
+
});
|
|
3364
|
+
return { fileId, fileName, mimeType, size: blob.size, imageUrl };
|
|
3365
|
+
} finally {
|
|
3366
|
+
clearTimeout(timeoutId);
|
|
3367
|
+
}
|
|
3368
|
+
})
|
|
3369
|
+
);
|
|
3370
|
+
results.forEach((result, i) => {
|
|
3371
|
+
const imageUrl = uniqueUrls[i];
|
|
3372
|
+
if (result.status === "fulfilled") {
|
|
3373
|
+
const { fileId, fileName, mimeType, size } = result.value;
|
|
3374
|
+
processedFiles.push({
|
|
3375
|
+
id: fileId,
|
|
3376
|
+
name: fileName,
|
|
3377
|
+
type: mimeType,
|
|
3378
|
+
size,
|
|
3379
|
+
sourceUrl: imageUrl
|
|
3380
|
+
});
|
|
3381
|
+
const placeholder = createFilePlaceholder(fileId);
|
|
3382
|
+
const escapedUrl = imageUrl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3383
|
+
const markdownImagePattern = new RegExp(
|
|
3384
|
+
`!\\[[^\\]]*\\]\\([\\s]*${escapedUrl}[\\s]*\\)`,
|
|
3385
|
+
"g"
|
|
3386
|
+
);
|
|
3387
|
+
cleanedContent = cleanedContent.replace(markdownImagePattern, placeholder);
|
|
3388
|
+
cleanedContent = cleanedContent.replace(new RegExp(escapedUrl, "g"), placeholder);
|
|
3389
|
+
} else {
|
|
3390
|
+
console.error("[extractAndStoreEncryptedMCPImages] Failed:", result.reason);
|
|
3391
|
+
}
|
|
3392
|
+
});
|
|
3393
|
+
cleanedContent = cleanedContent.replace(/\n{3,}/g, "\n\n").trim();
|
|
3394
|
+
return { processedFiles, cleanedContent };
|
|
3395
|
+
} catch (err) {
|
|
3396
|
+
console.error("[extractAndStoreEncryptedMCPImages] Unexpected error:", err);
|
|
3397
|
+
return { processedFiles: [], cleanedContent: content };
|
|
3398
|
+
}
|
|
3399
|
+
},
|
|
3400
|
+
[]
|
|
3401
|
+
);
|
|
3028
3402
|
const sendMessage = useCallback2(
|
|
3029
3403
|
async (args) => {
|
|
3030
3404
|
const {
|
|
@@ -3076,10 +3450,10 @@ function useChatStorage(options) {
|
|
|
3076
3450
|
const storedMessages = await getMessages(convId);
|
|
3077
3451
|
const validMessages = storedMessages.filter((msg) => !msg.error);
|
|
3078
3452
|
const limitedMessages = validMessages.slice(-maxHistoryMessages);
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
];
|
|
3453
|
+
const historyMessages = await Promise.all(
|
|
3454
|
+
limitedMessages.map(storedToLlmapiMessage)
|
|
3455
|
+
);
|
|
3456
|
+
messagesToSend = [...historyMessages, ...messages];
|
|
3083
3457
|
} else {
|
|
3084
3458
|
messagesToSend = [...messages];
|
|
3085
3459
|
}
|
|
@@ -3217,7 +3591,14 @@ function useChatStorage(options) {
|
|
|
3217
3591
|
let cleanedContent = assistantContent.replace(jsonSourcesBlockRegex, "").trim();
|
|
3218
3592
|
cleanedContent = cleanedContent.replace(/\n{3,}/g, "\n\n");
|
|
3219
3593
|
let processedFiles = [];
|
|
3220
|
-
if (
|
|
3594
|
+
if (walletAddress) {
|
|
3595
|
+
const result2 = await extractAndStoreEncryptedMCPImages(
|
|
3596
|
+
cleanedContent,
|
|
3597
|
+
walletAddress
|
|
3598
|
+
);
|
|
3599
|
+
processedFiles = result2.processedFiles;
|
|
3600
|
+
cleanedContent = result2.cleanedContent;
|
|
3601
|
+
} else if (writeFile) {
|
|
3221
3602
|
const result2 = await extractAndStoreMCPImages(
|
|
3222
3603
|
cleanedContent,
|
|
3223
3604
|
writeFile
|
|
@@ -3253,7 +3634,7 @@ function useChatStorage(options) {
|
|
|
3253
3634
|
assistantMessage: storedAssistantMessage
|
|
3254
3635
|
};
|
|
3255
3636
|
},
|
|
3256
|
-
[ensureConversation, getMessages, storageCtx, baseSendMessage]
|
|
3637
|
+
[ensureConversation, getMessages, storageCtx, baseSendMessage, walletAddress, extractAndStoreEncryptedMCPImages]
|
|
3257
3638
|
);
|
|
3258
3639
|
const searchMessages = useCallback2(
|
|
3259
3640
|
async (queryVector, options2) => {
|
|
@@ -3576,7 +3957,7 @@ var sdkModelClasses = [
|
|
|
3576
3957
|
];
|
|
3577
3958
|
|
|
3578
3959
|
// src/react/useMemoryStorage.ts
|
|
3579
|
-
import { useCallback as useCallback3, useState as useState3, useMemo as useMemo2, useRef as
|
|
3960
|
+
import { useCallback as useCallback3, useState as useState3, useMemo as useMemo2, useRef as useRef3 } from "react";
|
|
3580
3961
|
import { postApiV1ChatCompletions } from "@reverbia/sdk";
|
|
3581
3962
|
|
|
3582
3963
|
// src/lib/db/memory/schema.ts
|
|
@@ -4368,7 +4749,7 @@ function useMemoryStorage(options) {
|
|
|
4368
4749
|
} = options;
|
|
4369
4750
|
const embeddingModel = userEmbeddingModel === void 0 ? DEFAULT_API_EMBEDDING_MODEL : userEmbeddingModel;
|
|
4370
4751
|
const [memories, setMemories] = useState3([]);
|
|
4371
|
-
const extractionInProgressRef =
|
|
4752
|
+
const extractionInProgressRef = useRef3(false);
|
|
4372
4753
|
const memoriesCollection = useMemo2(
|
|
4373
4754
|
() => database.get("memories"),
|
|
4374
4755
|
[database]
|
|
@@ -4892,7 +5273,7 @@ function useMemoryStorage(options) {
|
|
|
4892
5273
|
|
|
4893
5274
|
// src/react/useSettings.ts
|
|
4894
5275
|
import { Q as Q5 } from "@nozbe/watermelondb";
|
|
4895
|
-
import { useCallback as useCallback4, useState as useState4, useMemo as useMemo3, useEffect as
|
|
5276
|
+
import { useCallback as useCallback4, useState as useState4, useMemo as useMemo3, useEffect as useEffect3 } from "react";
|
|
4896
5277
|
|
|
4897
5278
|
// src/lib/db/settings/schema.ts
|
|
4898
5279
|
import { appSchema as appSchema4, tableSchema as tableSchema4 } from "@nozbe/watermelondb";
|
|
@@ -5369,7 +5750,7 @@ function useSettings(options) {
|
|
|
5369
5750
|
},
|
|
5370
5751
|
[storageCtx, walletAddress]
|
|
5371
5752
|
);
|
|
5372
|
-
|
|
5753
|
+
useEffect3(() => {
|
|
5373
5754
|
if (!walletAddress) {
|
|
5374
5755
|
setModelPreferenceState(null);
|
|
5375
5756
|
setUserPreferenceState(null);
|
|
@@ -5407,7 +5788,7 @@ function useSettings(options) {
|
|
|
5407
5788
|
cancelled = true;
|
|
5408
5789
|
};
|
|
5409
5790
|
}, [walletAddress, storageCtx, legacyStorageCtx]);
|
|
5410
|
-
|
|
5791
|
+
useEffect3(() => {
|
|
5411
5792
|
if (!walletAddress) return;
|
|
5412
5793
|
const subscription = userPreferencesCollection.query(Q5.where("wallet_address", walletAddress)).observeWithColumns([
|
|
5413
5794
|
"nickname",
|
|
@@ -5648,22 +6029,22 @@ ${text5}`;
|
|
|
5648
6029
|
}
|
|
5649
6030
|
|
|
5650
6031
|
// src/react/useModels.ts
|
|
5651
|
-
import { useCallback as useCallback7, useEffect as
|
|
6032
|
+
import { useCallback as useCallback7, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
|
|
5652
6033
|
function useModels(options = {}) {
|
|
5653
6034
|
const { getToken, baseUrl = BASE_URL, provider, autoFetch = true } = options;
|
|
5654
6035
|
const [models, setModels] = useState7([]);
|
|
5655
6036
|
const [isLoading, setIsLoading] = useState7(false);
|
|
5656
6037
|
const [error, setError] = useState7(null);
|
|
5657
|
-
const getTokenRef =
|
|
5658
|
-
const baseUrlRef =
|
|
5659
|
-
const providerRef =
|
|
5660
|
-
const abortControllerRef =
|
|
5661
|
-
|
|
6038
|
+
const getTokenRef = useRef4(getToken);
|
|
6039
|
+
const baseUrlRef = useRef4(baseUrl);
|
|
6040
|
+
const providerRef = useRef4(provider);
|
|
6041
|
+
const abortControllerRef = useRef4(null);
|
|
6042
|
+
useEffect4(() => {
|
|
5662
6043
|
getTokenRef.current = getToken;
|
|
5663
6044
|
baseUrlRef.current = baseUrl;
|
|
5664
6045
|
providerRef.current = provider;
|
|
5665
6046
|
});
|
|
5666
|
-
|
|
6047
|
+
useEffect4(() => {
|
|
5667
6048
|
return () => {
|
|
5668
6049
|
if (abortControllerRef.current) {
|
|
5669
6050
|
abortControllerRef.current.abort();
|
|
@@ -5733,8 +6114,8 @@ function useModels(options = {}) {
|
|
|
5733
6114
|
setModels([]);
|
|
5734
6115
|
await fetchModels();
|
|
5735
6116
|
}, [fetchModels]);
|
|
5736
|
-
const hasFetchedRef =
|
|
5737
|
-
|
|
6117
|
+
const hasFetchedRef = useRef4(false);
|
|
6118
|
+
useEffect4(() => {
|
|
5738
6119
|
if (autoFetch && !hasFetchedRef.current) {
|
|
5739
6120
|
hasFetchedRef.current = true;
|
|
5740
6121
|
fetchModels();
|
|
@@ -5752,15 +6133,15 @@ function useModels(options = {}) {
|
|
|
5752
6133
|
}
|
|
5753
6134
|
|
|
5754
6135
|
// src/react/useSearch.ts
|
|
5755
|
-
import { useCallback as useCallback8, useEffect as
|
|
6136
|
+
import { useCallback as useCallback8, useEffect as useEffect5, useRef as useRef5, useState as useState8 } from "react";
|
|
5756
6137
|
function useSearch(options = {}) {
|
|
5757
6138
|
const { getToken, baseUrl = BASE_URL, onError } = options;
|
|
5758
6139
|
const [isLoading, setIsLoading] = useState8(false);
|
|
5759
6140
|
const [results, setResults] = useState8(null);
|
|
5760
6141
|
const [response, setResponse] = useState8(null);
|
|
5761
6142
|
const [error, setError] = useState8(null);
|
|
5762
|
-
const abortControllerRef =
|
|
5763
|
-
|
|
6143
|
+
const abortControllerRef = useRef5(null);
|
|
6144
|
+
useEffect5(() => {
|
|
5764
6145
|
return () => {
|
|
5765
6146
|
if (abortControllerRef.current) {
|
|
5766
6147
|
abortControllerRef.current.abort();
|
|
@@ -5836,12 +6217,12 @@ function useSearch(options = {}) {
|
|
|
5836
6217
|
}
|
|
5837
6218
|
|
|
5838
6219
|
// src/react/useImageGeneration.ts
|
|
5839
|
-
import { useCallback as useCallback9, useEffect as
|
|
6220
|
+
import { useCallback as useCallback9, useEffect as useEffect6, useRef as useRef6, useState as useState9 } from "react";
|
|
5840
6221
|
function useImageGeneration(options = {}) {
|
|
5841
6222
|
const { getToken, baseUrl = BASE_URL, onFinish, onError } = options;
|
|
5842
6223
|
const [isLoading, setIsLoading] = useState9(false);
|
|
5843
|
-
const abortControllerRef =
|
|
5844
|
-
|
|
6224
|
+
const abortControllerRef = useRef6(null);
|
|
6225
|
+
useEffect6(() => {
|
|
5845
6226
|
return () => {
|
|
5846
6227
|
if (abortControllerRef.current) {
|
|
5847
6228
|
abortControllerRef.current.abort();
|
|
@@ -6223,7 +6604,7 @@ import {
|
|
|
6223
6604
|
createElement,
|
|
6224
6605
|
useCallback as useCallback10,
|
|
6225
6606
|
useContext,
|
|
6226
|
-
useEffect as
|
|
6607
|
+
useEffect as useEffect7,
|
|
6227
6608
|
useState as useState10
|
|
6228
6609
|
} from "react";
|
|
6229
6610
|
|
|
@@ -6554,7 +6935,7 @@ function DropboxAuthProvider({
|
|
|
6554
6935
|
}) {
|
|
6555
6936
|
const [accessToken, setAccessToken] = useState10(null);
|
|
6556
6937
|
const isConfigured = !!appKey;
|
|
6557
|
-
|
|
6938
|
+
useEffect7(() => {
|
|
6558
6939
|
const checkStoredToken = async () => {
|
|
6559
6940
|
if (walletAddress) {
|
|
6560
6941
|
await migrateUnencryptedTokens("dropbox", walletAddress);
|
|
@@ -6568,7 +6949,7 @@ function DropboxAuthProvider({
|
|
|
6568
6949
|
};
|
|
6569
6950
|
checkStoredToken();
|
|
6570
6951
|
}, [apiClient, walletAddress]);
|
|
6571
|
-
|
|
6952
|
+
useEffect7(() => {
|
|
6572
6953
|
if (!isConfigured) return;
|
|
6573
6954
|
const handleCallback = async () => {
|
|
6574
6955
|
if (isDropboxCallback()) {
|
|
@@ -6740,7 +7121,7 @@ import {
|
|
|
6740
7121
|
createElement as createElement2,
|
|
6741
7122
|
useCallback as useCallback12,
|
|
6742
7123
|
useContext as useContext2,
|
|
6743
|
-
useEffect as
|
|
7124
|
+
useEffect as useEffect8,
|
|
6744
7125
|
useState as useState11
|
|
6745
7126
|
} from "react";
|
|
6746
7127
|
|
|
@@ -6974,7 +7355,7 @@ function GoogleDriveAuthProvider({
|
|
|
6974
7355
|
}) {
|
|
6975
7356
|
const [accessToken, setAccessToken] = useState11(null);
|
|
6976
7357
|
const isConfigured = !!clientId;
|
|
6977
|
-
|
|
7358
|
+
useEffect8(() => {
|
|
6978
7359
|
const checkStoredToken = async () => {
|
|
6979
7360
|
if (walletAddress) {
|
|
6980
7361
|
await migrateUnencryptedTokens("google-drive", walletAddress);
|
|
@@ -6988,7 +7369,7 @@ function GoogleDriveAuthProvider({
|
|
|
6988
7369
|
};
|
|
6989
7370
|
checkStoredToken();
|
|
6990
7371
|
}, [apiClient, walletAddress]);
|
|
6991
|
-
|
|
7372
|
+
useEffect8(() => {
|
|
6992
7373
|
if (!isConfigured) return;
|
|
6993
7374
|
const handleCallback = async () => {
|
|
6994
7375
|
if (isGoogleDriveCallback()) {
|
|
@@ -7471,7 +7852,7 @@ import {
|
|
|
7471
7852
|
createElement as createElement3,
|
|
7472
7853
|
useCallback as useCallback14,
|
|
7473
7854
|
useContext as useContext3,
|
|
7474
|
-
useEffect as
|
|
7855
|
+
useEffect as useEffect9,
|
|
7475
7856
|
useState as useState12
|
|
7476
7857
|
} from "react";
|
|
7477
7858
|
|
|
@@ -7721,7 +8102,7 @@ function ICloudAuthProvider({
|
|
|
7721
8102
|
const [isAvailable, setIsAvailable] = useState12(false);
|
|
7722
8103
|
const [isConfigured, setIsConfigured] = useState12(false);
|
|
7723
8104
|
const [isLoading, setIsLoading] = useState12(false);
|
|
7724
|
-
|
|
8105
|
+
useEffect9(() => {
|
|
7725
8106
|
if (!apiToken || typeof window === "undefined") {
|
|
7726
8107
|
return;
|
|
7727
8108
|
}
|
|
@@ -8037,7 +8418,7 @@ import {
|
|
|
8037
8418
|
createElement as createElement4,
|
|
8038
8419
|
useCallback as useCallback16,
|
|
8039
8420
|
useContext as useContext4,
|
|
8040
|
-
useEffect as
|
|
8421
|
+
useEffect as useEffect10,
|
|
8041
8422
|
useState as useState13
|
|
8042
8423
|
} from "react";
|
|
8043
8424
|
var BackupAuthContext = createContext4(null);
|
|
@@ -8061,7 +8442,7 @@ function BackupAuthProvider({
|
|
|
8061
8442
|
const [icloudUserRecordName, setIcloudUserRecordName] = useState13(null);
|
|
8062
8443
|
const [isIcloudAvailable, setIsIcloudAvailable] = useState13(false);
|
|
8063
8444
|
const isIcloudConfigured = isIcloudAvailable && !!icloudApiToken;
|
|
8064
|
-
|
|
8445
|
+
useEffect10(() => {
|
|
8065
8446
|
const checkStoredTokens = async () => {
|
|
8066
8447
|
if (walletAddress) {
|
|
8067
8448
|
await Promise.all([
|
|
@@ -8084,7 +8465,7 @@ function BackupAuthProvider({
|
|
|
8084
8465
|
};
|
|
8085
8466
|
checkStoredTokens();
|
|
8086
8467
|
}, [apiClient, walletAddress]);
|
|
8087
|
-
|
|
8468
|
+
useEffect10(() => {
|
|
8088
8469
|
if (!icloudApiToken || typeof window === "undefined") {
|
|
8089
8470
|
return;
|
|
8090
8471
|
}
|
|
@@ -8112,7 +8493,7 @@ function BackupAuthProvider({
|
|
|
8112
8493
|
};
|
|
8113
8494
|
initCloudKit();
|
|
8114
8495
|
}, [icloudApiToken, icloudContainerIdentifier, icloudEnvironment]);
|
|
8115
|
-
|
|
8496
|
+
useEffect10(() => {
|
|
8116
8497
|
if (!isDropboxConfigured) return;
|
|
8117
8498
|
const handleCallback = async () => {
|
|
8118
8499
|
if (isDropboxCallback()) {
|
|
@@ -8132,7 +8513,7 @@ function BackupAuthProvider({
|
|
|
8132
8513
|
};
|
|
8133
8514
|
handleCallback();
|
|
8134
8515
|
}, [dropboxCallbackPath, isDropboxConfigured, apiClient, walletAddress]);
|
|
8135
|
-
|
|
8516
|
+
useEffect10(() => {
|
|
8136
8517
|
if (!isGoogleConfigured) return;
|
|
8137
8518
|
const handleCallback = async () => {
|
|
8138
8519
|
if (isGoogleDriveCallback()) {
|
|
@@ -9060,6 +9441,7 @@ export {
|
|
|
9060
9441
|
DEFAULT_ROOT_FOLDER as BACKUP_DRIVE_ROOT_FOLDER,
|
|
9061
9442
|
DEFAULT_BACKUP_FOLDER2 as BACKUP_ICLOUD_FOLDER,
|
|
9062
9443
|
BackupAuthProvider,
|
|
9444
|
+
BlobUrlManager,
|
|
9063
9445
|
Conversation as ChatConversation,
|
|
9064
9446
|
Message as ChatMessage,
|
|
9065
9447
|
DEFAULT_BACKUP_FOLDER,
|
|
@@ -9089,9 +9471,12 @@ export {
|
|
|
9089
9471
|
createMemoryContextSystemMessage,
|
|
9090
9472
|
decryptData,
|
|
9091
9473
|
decryptDataBytes,
|
|
9474
|
+
deleteEncryptedFile,
|
|
9092
9475
|
encryptData,
|
|
9093
9476
|
exportPublicKey,
|
|
9094
9477
|
extractConversationContext,
|
|
9478
|
+
fileExists,
|
|
9479
|
+
findFileIdBySourceUrl,
|
|
9095
9480
|
formatMemoriesForChat,
|
|
9096
9481
|
generateCompositeKey,
|
|
9097
9482
|
generateConversationId,
|
|
@@ -9102,6 +9487,7 @@ export {
|
|
|
9102
9487
|
getAndClearDriveReturnUrl,
|
|
9103
9488
|
getCalendarAccessToken,
|
|
9104
9489
|
getDriveAccessToken,
|
|
9490
|
+
getEncryptionKey,
|
|
9105
9491
|
getGoogleDriveStoredToken,
|
|
9106
9492
|
getValidCalendarToken,
|
|
9107
9493
|
getValidDriveToken,
|
|
@@ -9116,9 +9502,12 @@ export {
|
|
|
9116
9502
|
hasKeyPair,
|
|
9117
9503
|
isCalendarCallback,
|
|
9118
9504
|
isDriveCallback,
|
|
9505
|
+
isOPFSSupported,
|
|
9119
9506
|
memoryStorageSchema,
|
|
9507
|
+
readEncryptedFile,
|
|
9120
9508
|
refreshCalendarToken,
|
|
9121
9509
|
refreshDriveToken,
|
|
9510
|
+
replaceUrlWithMCPPlaceholder,
|
|
9122
9511
|
requestEncryptionKey,
|
|
9123
9512
|
requestKeyPair,
|
|
9124
9513
|
revokeCalendarToken,
|
|
@@ -9153,5 +9542,6 @@ export {
|
|
|
9153
9542
|
usePdf,
|
|
9154
9543
|
useSearch,
|
|
9155
9544
|
useSettings,
|
|
9156
|
-
userPreferencesStorageSchema
|
|
9545
|
+
userPreferencesStorageSchema,
|
|
9546
|
+
writeEncryptedFile
|
|
9157
9547
|
};
|