@reverbia/sdk 1.0.0-next.20251124100226 → 1.0.0-next.20251125084024
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/react/index.cjs +706 -617
- package/dist/react/index.d.mts +57 -3
- package/dist/react/index.d.ts +57 -3
- package/dist/react/index.mjs +452 -365
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -1,288 +1,5 @@
|
|
|
1
1
|
// src/react/useChat.ts
|
|
2
|
-
import { useCallback, useState } from "react";
|
|
3
|
-
import {
|
|
4
|
-
postApiV1ChatCompletions
|
|
5
|
-
} from "@reverbia/sdk";
|
|
6
|
-
function useChat(options) {
|
|
7
|
-
const { getToken } = options || {};
|
|
8
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
9
|
-
const sendMessage = useCallback(
|
|
10
|
-
async ({
|
|
11
|
-
messages,
|
|
12
|
-
model
|
|
13
|
-
}) => {
|
|
14
|
-
if (!messages?.length) {
|
|
15
|
-
const error = "messages are required to call sendMessage.";
|
|
16
|
-
return { data: null, error };
|
|
17
|
-
}
|
|
18
|
-
if (!model) {
|
|
19
|
-
const error = "model is required to call sendMessage.";
|
|
20
|
-
return { data: null, error };
|
|
21
|
-
}
|
|
22
|
-
if (!getToken) {
|
|
23
|
-
const error = "Token getter function is required.";
|
|
24
|
-
return { data: null, error };
|
|
25
|
-
}
|
|
26
|
-
setIsLoading(true);
|
|
27
|
-
try {
|
|
28
|
-
const token = await getToken();
|
|
29
|
-
if (!token) {
|
|
30
|
-
const error = "No access token available.";
|
|
31
|
-
setIsLoading(false);
|
|
32
|
-
return { data: null, error };
|
|
33
|
-
}
|
|
34
|
-
const completion = await postApiV1ChatCompletions({
|
|
35
|
-
body: {
|
|
36
|
-
messages,
|
|
37
|
-
model
|
|
38
|
-
},
|
|
39
|
-
headers: {
|
|
40
|
-
Authorization: `Bearer ${token}`
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
if (!completion.data) {
|
|
44
|
-
const error = completion.error?.error ?? "API did not return a completion response.";
|
|
45
|
-
setIsLoading(false);
|
|
46
|
-
return { data: null, error };
|
|
47
|
-
}
|
|
48
|
-
if (typeof completion.data === "string") {
|
|
49
|
-
const error = "API returned a string response instead of a completion object.";
|
|
50
|
-
setIsLoading(false);
|
|
51
|
-
return { data: null, error };
|
|
52
|
-
}
|
|
53
|
-
setIsLoading(false);
|
|
54
|
-
return { data: completion.data, error: null };
|
|
55
|
-
} catch (err) {
|
|
56
|
-
const error = err instanceof Error ? err.message : "Failed to send message.";
|
|
57
|
-
setIsLoading(false);
|
|
58
|
-
return { data: null, error };
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
[getToken]
|
|
62
|
-
);
|
|
63
|
-
return {
|
|
64
|
-
isLoading,
|
|
65
|
-
sendMessage
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// src/react/useMemory.ts
|
|
70
|
-
import { useCallback as useCallback2, useRef } from "react";
|
|
71
|
-
import { postApiV1ChatCompletions as postApiV1ChatCompletions2 } from "@reverbia/sdk";
|
|
72
|
-
|
|
73
|
-
// src/lib/memory/service.ts
|
|
74
|
-
var FACT_EXTRACTION_PROMPT = `You are a memory extraction system. Extract durable user memories from chat messages.
|
|
75
|
-
|
|
76
|
-
CRITICAL: You MUST respond with ONLY valid JSON. No explanations, no markdown, no code blocks, just pure JSON.
|
|
77
|
-
|
|
78
|
-
Only extract facts that will be useful in future conversations, such as identity, stable preferences, ongoing projects, skills, and constraints.
|
|
79
|
-
|
|
80
|
-
Do not extract sensitive attributes, temporary things, or single-use instructions.
|
|
81
|
-
|
|
82
|
-
If there are no memories to extract, return: {"items": []}
|
|
83
|
-
|
|
84
|
-
Response format (JSON only, no other text):
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
"items": [
|
|
88
|
-
{
|
|
89
|
-
"type": "identity",
|
|
90
|
-
"namespace": "identity",
|
|
91
|
-
"key": "name",
|
|
92
|
-
"value": "Charlie",
|
|
93
|
-
"rawEvidence": "I'm Charlie",
|
|
94
|
-
"confidence": 0.98,
|
|
95
|
-
"pii": true
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"type": "identity",
|
|
99
|
-
"namespace": "work",
|
|
100
|
-
"key": "company",
|
|
101
|
-
"value": "ZetaChain",
|
|
102
|
-
"rawEvidence": "called ZetaChain",
|
|
103
|
-
"confidence": 0.99,
|
|
104
|
-
"pii": false
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"type": "preference",
|
|
108
|
-
"namespace": "answer_style",
|
|
109
|
-
"key": "verbosity",
|
|
110
|
-
"value": "concise_direct",
|
|
111
|
-
"rawEvidence": "I prefer concise, direct answers",
|
|
112
|
-
"confidence": 0.96,
|
|
113
|
-
"pii": false
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
"type": "identity",
|
|
117
|
-
"namespace": "timezone",
|
|
118
|
-
"key": "tz",
|
|
119
|
-
"value": "America/Los_Angeles",
|
|
120
|
-
"rawEvidence": "I'm in PST",
|
|
121
|
-
"confidence": 0.9,
|
|
122
|
-
"pii": false
|
|
123
|
-
}
|
|
124
|
-
]
|
|
125
|
-
}`;
|
|
126
|
-
var preprocessMemories = (items, minConfidence = 0.6) => {
|
|
127
|
-
if (!items || !Array.isArray(items)) {
|
|
128
|
-
return [];
|
|
129
|
-
}
|
|
130
|
-
const validItems = items.filter((item) => {
|
|
131
|
-
if (item.namespace == null || item.key == null || item.value == null) {
|
|
132
|
-
console.warn(
|
|
133
|
-
"Dropping memory item with null/undefined namespace, key, or value:",
|
|
134
|
-
item
|
|
135
|
-
);
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
const namespace = String(item.namespace).trim();
|
|
139
|
-
const key = String(item.key).trim();
|
|
140
|
-
const value = String(item.value).trim();
|
|
141
|
-
if (namespace === "" || key === "" || value === "") {
|
|
142
|
-
console.warn(
|
|
143
|
-
"Dropping memory item with empty namespace, key, or value after trimming:",
|
|
144
|
-
item
|
|
145
|
-
);
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
if (typeof item.confidence !== "number" || item.confidence < minConfidence) {
|
|
149
|
-
console.warn(
|
|
150
|
-
`Dropping memory item with confidence ${item.confidence} below threshold ${minConfidence}:`,
|
|
151
|
-
item
|
|
152
|
-
);
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
return true;
|
|
156
|
-
});
|
|
157
|
-
const deduplicatedMap = /* @__PURE__ */ new Map();
|
|
158
|
-
for (const item of validItems) {
|
|
159
|
-
const uniqueKey = `${item.namespace}:${item.key}:${item.value}`;
|
|
160
|
-
const existing = deduplicatedMap.get(uniqueKey);
|
|
161
|
-
if (!existing || item.confidence > existing.confidence) {
|
|
162
|
-
deduplicatedMap.set(uniqueKey, item);
|
|
163
|
-
} else {
|
|
164
|
-
console.debug(
|
|
165
|
-
`Deduplicating memory item: keeping entry with higher confidence (${existing.confidence} > ${item.confidence})`,
|
|
166
|
-
{ namespace: item.namespace, key: item.key, value: item.value }
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return Array.from(deduplicatedMap.values());
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
// src/lib/memory/db.ts
|
|
174
|
-
import Dexie from "dexie";
|
|
175
|
-
var MemoryDatabase = class extends Dexie {
|
|
176
|
-
constructor() {
|
|
177
|
-
super("MemoryDatabase");
|
|
178
|
-
this.version(2).stores({
|
|
179
|
-
memories: "++id, uniqueKey, compositeKey, namespace, key, type, createdAt, updatedAt"
|
|
180
|
-
});
|
|
181
|
-
this.version(3).stores({
|
|
182
|
-
memories: "++id, uniqueKey, compositeKey, namespace, key, type, createdAt, updatedAt"
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
var memoryDb = new MemoryDatabase();
|
|
187
|
-
var saveMemory = async (memory) => {
|
|
188
|
-
const compositeKey = `${memory.namespace}:${memory.key}`;
|
|
189
|
-
const uniqueKey = `${memory.namespace}:${memory.key}:${memory.value}`;
|
|
190
|
-
const now = Date.now();
|
|
191
|
-
const existing = await memoryDb.memories.where("uniqueKey").equals(uniqueKey).first();
|
|
192
|
-
if (existing) {
|
|
193
|
-
const shouldPreserveEmbedding = existing.value === memory.value && existing.rawEvidence === memory.rawEvidence && existing.type === memory.type && existing.namespace === memory.namespace && existing.key === memory.key && existing.embedding !== void 0 && existing.embedding.length > 0;
|
|
194
|
-
const updateData = {
|
|
195
|
-
...memory,
|
|
196
|
-
compositeKey,
|
|
197
|
-
uniqueKey,
|
|
198
|
-
updatedAt: now,
|
|
199
|
-
createdAt: existing.createdAt
|
|
200
|
-
};
|
|
201
|
-
if (shouldPreserveEmbedding) {
|
|
202
|
-
updateData.embedding = existing.embedding;
|
|
203
|
-
updateData.embeddingModel = existing.embeddingModel;
|
|
204
|
-
} else {
|
|
205
|
-
updateData.embedding = [];
|
|
206
|
-
updateData.embeddingModel = void 0;
|
|
207
|
-
}
|
|
208
|
-
await memoryDb.memories.update(existing.id, updateData);
|
|
209
|
-
} else {
|
|
210
|
-
await memoryDb.memories.add({
|
|
211
|
-
...memory,
|
|
212
|
-
compositeKey,
|
|
213
|
-
uniqueKey,
|
|
214
|
-
createdAt: now,
|
|
215
|
-
updatedAt: now
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
var saveMemories = async (memories) => {
|
|
220
|
-
await Promise.all(memories.map((memory) => saveMemory(memory)));
|
|
221
|
-
};
|
|
222
|
-
var getAllMemories = async () => {
|
|
223
|
-
return memoryDb.memories.toArray();
|
|
224
|
-
};
|
|
225
|
-
var cosineSimilarity = (a, b) => {
|
|
226
|
-
if (a.length !== b.length) {
|
|
227
|
-
throw new Error("Vectors must have the same length");
|
|
228
|
-
}
|
|
229
|
-
let dotProduct = 0;
|
|
230
|
-
let normA = 0;
|
|
231
|
-
let normB = 0;
|
|
232
|
-
for (let i = 0; i < a.length; i++) {
|
|
233
|
-
dotProduct += a[i] * b[i];
|
|
234
|
-
normA += a[i] * a[i];
|
|
235
|
-
normB += b[i] * b[i];
|
|
236
|
-
}
|
|
237
|
-
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
238
|
-
if (denominator === 0) {
|
|
239
|
-
return 0;
|
|
240
|
-
}
|
|
241
|
-
return dotProduct / denominator;
|
|
242
|
-
};
|
|
243
|
-
var searchSimilarMemories = async (queryEmbedding, limit = 10, minSimilarity = 0.6) => {
|
|
244
|
-
const allMemories = await getAllMemories();
|
|
245
|
-
const memoriesWithEmbeddings = allMemories.filter(
|
|
246
|
-
(m) => m.embedding && m.embedding.length > 0
|
|
247
|
-
);
|
|
248
|
-
console.log(
|
|
249
|
-
`[Memory Search] Total memories: ${allMemories.length}, memories with embeddings: ${memoriesWithEmbeddings.length}`
|
|
250
|
-
);
|
|
251
|
-
if (memoriesWithEmbeddings.length === 0) {
|
|
252
|
-
console.warn(
|
|
253
|
-
"[Memory Search] No memories with embeddings found. Memories may need embeddings generated. Use generateAndStoreEmbeddings() to generate embeddings for existing memories."
|
|
254
|
-
);
|
|
255
|
-
return [];
|
|
256
|
-
}
|
|
257
|
-
const allResults = memoriesWithEmbeddings.map((memory) => {
|
|
258
|
-
const similarity = cosineSimilarity(queryEmbedding, memory.embedding);
|
|
259
|
-
return {
|
|
260
|
-
...memory,
|
|
261
|
-
similarity
|
|
262
|
-
};
|
|
263
|
-
}).sort((a, b) => b.similarity - a.similarity);
|
|
264
|
-
console.log(
|
|
265
|
-
`[Memory Search] All similarity scores:`,
|
|
266
|
-
allResults.map((r) => ({
|
|
267
|
-
key: `${r.namespace}:${r.key}`,
|
|
268
|
-
value: r.value,
|
|
269
|
-
similarity: r.similarity.toFixed(4)
|
|
270
|
-
}))
|
|
271
|
-
);
|
|
272
|
-
const results = allResults.filter((result) => result.similarity >= minSimilarity).slice(0, limit);
|
|
273
|
-
if (results.length === 0 && allResults.length > 0) {
|
|
274
|
-
const topSimilarity = allResults[0].similarity;
|
|
275
|
-
const suggestedThreshold = Math.max(0.3, topSimilarity - 0.1);
|
|
276
|
-
console.warn(
|
|
277
|
-
`[Memory Search] No memories above threshold ${minSimilarity}. Highest similarity was ${topSimilarity.toFixed(4)}. Consider lowering the threshold to ${suggestedThreshold.toFixed(2)}`
|
|
278
|
-
);
|
|
279
|
-
} else {
|
|
280
|
-
console.log(
|
|
281
|
-
`[Memory Search] Found ${results.length} memories above similarity threshold ${minSimilarity}. Top similarity: ${results[0]?.similarity.toFixed(4) || "N/A"}`
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
return results;
|
|
285
|
-
};
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
286
3
|
|
|
287
4
|
// src/client/core/bodySerializer.gen.ts
|
|
288
5
|
var jsonBodySerializer = {
|
|
@@ -1012,91 +729,461 @@ var createClient = (config = {}) => {
|
|
|
1012
729
|
...result
|
|
1013
730
|
};
|
|
1014
731
|
}
|
|
1015
|
-
const textError = await response.text();
|
|
1016
|
-
let jsonError;
|
|
1017
|
-
try {
|
|
1018
|
-
jsonError = JSON.parse(textError);
|
|
1019
|
-
} catch {
|
|
732
|
+
const textError = await response.text();
|
|
733
|
+
let jsonError;
|
|
734
|
+
try {
|
|
735
|
+
jsonError = JSON.parse(textError);
|
|
736
|
+
} catch {
|
|
737
|
+
}
|
|
738
|
+
const error = jsonError ?? textError;
|
|
739
|
+
let finalError = error;
|
|
740
|
+
for (const fn of interceptors.error.fns) {
|
|
741
|
+
if (fn) {
|
|
742
|
+
finalError = await fn(error, response, opts);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
finalError = finalError || {};
|
|
746
|
+
if (opts.throwOnError) {
|
|
747
|
+
throw finalError;
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
error: finalError,
|
|
751
|
+
...result
|
|
752
|
+
};
|
|
753
|
+
};
|
|
754
|
+
const makeMethodFn = (method) => (options) => request({ ...options, method });
|
|
755
|
+
const makeSseFn = (method) => async (options) => {
|
|
756
|
+
const { opts, url } = await beforeRequest(options);
|
|
757
|
+
return createSseClient({
|
|
758
|
+
...opts,
|
|
759
|
+
body: opts.body,
|
|
760
|
+
headers: opts.headers,
|
|
761
|
+
method,
|
|
762
|
+
onRequest: async (url2, init) => {
|
|
763
|
+
let request2 = new Request(url2, init);
|
|
764
|
+
const requestInit = {
|
|
765
|
+
...init,
|
|
766
|
+
method: init.method,
|
|
767
|
+
url: url2
|
|
768
|
+
};
|
|
769
|
+
for (const fn of interceptors.request.fns) {
|
|
770
|
+
if (fn) {
|
|
771
|
+
await fn(requestInit);
|
|
772
|
+
request2 = new Request(requestInit.url, requestInit);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return request2;
|
|
776
|
+
},
|
|
777
|
+
url
|
|
778
|
+
});
|
|
779
|
+
};
|
|
780
|
+
return {
|
|
781
|
+
buildUrl,
|
|
782
|
+
connect: makeMethodFn("CONNECT"),
|
|
783
|
+
delete: makeMethodFn("DELETE"),
|
|
784
|
+
get: makeMethodFn("GET"),
|
|
785
|
+
getConfig,
|
|
786
|
+
head: makeMethodFn("HEAD"),
|
|
787
|
+
interceptors,
|
|
788
|
+
options: makeMethodFn("OPTIONS"),
|
|
789
|
+
patch: makeMethodFn("PATCH"),
|
|
790
|
+
post: makeMethodFn("POST"),
|
|
791
|
+
put: makeMethodFn("PUT"),
|
|
792
|
+
request,
|
|
793
|
+
setConfig,
|
|
794
|
+
sse: {
|
|
795
|
+
connect: makeSseFn("CONNECT"),
|
|
796
|
+
delete: makeSseFn("DELETE"),
|
|
797
|
+
get: makeSseFn("GET"),
|
|
798
|
+
head: makeSseFn("HEAD"),
|
|
799
|
+
options: makeSseFn("OPTIONS"),
|
|
800
|
+
patch: makeSseFn("PATCH"),
|
|
801
|
+
post: makeSseFn("POST"),
|
|
802
|
+
put: makeSseFn("PUT"),
|
|
803
|
+
trace: makeSseFn("TRACE")
|
|
804
|
+
},
|
|
805
|
+
trace: makeMethodFn("TRACE")
|
|
806
|
+
};
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
// src/clientConfig.ts
|
|
810
|
+
var createClientConfig = (config) => ({
|
|
811
|
+
...config,
|
|
812
|
+
baseUrl: "https://ai-portal-dev.zetachain.com"
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
// src/client/client.gen.ts
|
|
816
|
+
var client = createClient(createClientConfig(createConfig()));
|
|
817
|
+
|
|
818
|
+
// src/react/useChat.ts
|
|
819
|
+
function useChat(options) {
|
|
820
|
+
const { getToken, onData: globalOnData, onFinish, onError } = options || {};
|
|
821
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
822
|
+
const abortControllerRef = useRef(null);
|
|
823
|
+
const stop = useCallback(() => {
|
|
824
|
+
if (abortControllerRef.current) {
|
|
825
|
+
abortControllerRef.current.abort();
|
|
826
|
+
abortControllerRef.current = null;
|
|
827
|
+
}
|
|
828
|
+
}, []);
|
|
829
|
+
useEffect(() => {
|
|
830
|
+
return () => {
|
|
831
|
+
if (abortControllerRef.current) {
|
|
832
|
+
abortControllerRef.current.abort();
|
|
833
|
+
abortControllerRef.current = null;
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
}, []);
|
|
837
|
+
const sendMessage = useCallback(
|
|
838
|
+
async ({
|
|
839
|
+
messages,
|
|
840
|
+
model,
|
|
841
|
+
onData
|
|
842
|
+
}) => {
|
|
843
|
+
if (!messages?.length) {
|
|
844
|
+
const errorMsg = "messages are required to call sendMessage.";
|
|
845
|
+
if (onError) onError(new Error(errorMsg));
|
|
846
|
+
return { data: null, error: errorMsg };
|
|
847
|
+
}
|
|
848
|
+
if (!model) {
|
|
849
|
+
const errorMsg = "model is required to call sendMessage.";
|
|
850
|
+
if (onError) onError(new Error(errorMsg));
|
|
851
|
+
return { data: null, error: errorMsg };
|
|
852
|
+
}
|
|
853
|
+
if (!getToken) {
|
|
854
|
+
const errorMsg = "Token getter function is required.";
|
|
855
|
+
if (onError) onError(new Error(errorMsg));
|
|
856
|
+
return { data: null, error: errorMsg };
|
|
857
|
+
}
|
|
858
|
+
if (abortControllerRef.current) {
|
|
859
|
+
abortControllerRef.current.abort();
|
|
860
|
+
}
|
|
861
|
+
const abortController = new AbortController();
|
|
862
|
+
abortControllerRef.current = abortController;
|
|
863
|
+
setIsLoading(true);
|
|
864
|
+
try {
|
|
865
|
+
const token = await getToken();
|
|
866
|
+
if (!token) {
|
|
867
|
+
const errorMsg = "No access token available.";
|
|
868
|
+
setIsLoading(false);
|
|
869
|
+
if (onError) onError(new Error(errorMsg));
|
|
870
|
+
return { data: null, error: errorMsg };
|
|
871
|
+
}
|
|
872
|
+
const sseResult = await client.sse.post({
|
|
873
|
+
url: "/api/v1/chat/completions",
|
|
874
|
+
body: {
|
|
875
|
+
messages,
|
|
876
|
+
model,
|
|
877
|
+
stream: true
|
|
878
|
+
},
|
|
879
|
+
headers: {
|
|
880
|
+
"Content-Type": "application/json",
|
|
881
|
+
Authorization: `Bearer ${token}`
|
|
882
|
+
},
|
|
883
|
+
signal: abortController.signal
|
|
884
|
+
});
|
|
885
|
+
let accumulatedContent = "";
|
|
886
|
+
let completionId = "";
|
|
887
|
+
let completionModel = "";
|
|
888
|
+
let usage;
|
|
889
|
+
let finishReason;
|
|
890
|
+
for await (const chunk of sseResult.stream) {
|
|
891
|
+
if (typeof chunk === "string" && (chunk.trim() === "[DONE]" || chunk.includes("[DONE]"))) {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (chunk && typeof chunk === "object") {
|
|
895
|
+
const chunkData = chunk;
|
|
896
|
+
if (chunkData.id && !completionId) {
|
|
897
|
+
completionId = chunkData.id;
|
|
898
|
+
}
|
|
899
|
+
if (chunkData.model && !completionModel) {
|
|
900
|
+
completionModel = chunkData.model;
|
|
901
|
+
}
|
|
902
|
+
if (chunkData.usage) {
|
|
903
|
+
usage = chunkData.usage;
|
|
904
|
+
}
|
|
905
|
+
if (chunkData.choices && Array.isArray(chunkData.choices) && chunkData.choices.length > 0) {
|
|
906
|
+
const choice = chunkData.choices[0];
|
|
907
|
+
if (choice.delta?.content) {
|
|
908
|
+
const content = choice.delta.content;
|
|
909
|
+
accumulatedContent += content;
|
|
910
|
+
if (onData) {
|
|
911
|
+
onData(content);
|
|
912
|
+
}
|
|
913
|
+
if (globalOnData) {
|
|
914
|
+
globalOnData(content);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (choice.finish_reason) {
|
|
918
|
+
finishReason = choice.finish_reason;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const completion = {
|
|
924
|
+
id: completionId,
|
|
925
|
+
model: completionModel,
|
|
926
|
+
choices: [
|
|
927
|
+
{
|
|
928
|
+
index: 0,
|
|
929
|
+
message: {
|
|
930
|
+
role: "assistant",
|
|
931
|
+
content: accumulatedContent
|
|
932
|
+
},
|
|
933
|
+
finish_reason: finishReason
|
|
934
|
+
}
|
|
935
|
+
],
|
|
936
|
+
usage
|
|
937
|
+
};
|
|
938
|
+
setIsLoading(false);
|
|
939
|
+
if (onFinish) {
|
|
940
|
+
onFinish(completion);
|
|
941
|
+
}
|
|
942
|
+
return { data: completion, error: null };
|
|
943
|
+
} catch (err) {
|
|
944
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
945
|
+
setIsLoading(false);
|
|
946
|
+
return { data: null, error: "Request aborted" };
|
|
947
|
+
}
|
|
948
|
+
const errorMsg = err instanceof Error ? err.message : "Failed to send message.";
|
|
949
|
+
const errorObj = err instanceof Error ? err : new Error(errorMsg);
|
|
950
|
+
setIsLoading(false);
|
|
951
|
+
if (onError) {
|
|
952
|
+
onError(errorObj);
|
|
953
|
+
}
|
|
954
|
+
return { data: null, error: errorMsg };
|
|
955
|
+
} finally {
|
|
956
|
+
if (abortControllerRef.current === abortController) {
|
|
957
|
+
abortControllerRef.current = null;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
[getToken, globalOnData, onFinish, onError]
|
|
962
|
+
);
|
|
963
|
+
return {
|
|
964
|
+
isLoading,
|
|
965
|
+
sendMessage,
|
|
966
|
+
stop
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/react/useMemory.ts
|
|
971
|
+
import { useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
972
|
+
import { postApiV1ChatCompletions } from "@reverbia/sdk";
|
|
973
|
+
|
|
974
|
+
// src/lib/memory/service.ts
|
|
975
|
+
var FACT_EXTRACTION_PROMPT = `You are a memory extraction system. Extract durable user memories from chat messages.
|
|
976
|
+
|
|
977
|
+
CRITICAL: You MUST respond with ONLY valid JSON. No explanations, no markdown, no code blocks, just pure JSON.
|
|
978
|
+
|
|
979
|
+
Only extract facts that will be useful in future conversations, such as identity, stable preferences, ongoing projects, skills, and constraints.
|
|
980
|
+
|
|
981
|
+
Do not extract sensitive attributes, temporary things, or single-use instructions.
|
|
982
|
+
|
|
983
|
+
If there are no memories to extract, return: {"items": []}
|
|
984
|
+
|
|
985
|
+
Response format (JSON only, no other text):
|
|
986
|
+
|
|
987
|
+
{
|
|
988
|
+
"items": [
|
|
989
|
+
{
|
|
990
|
+
"type": "identity",
|
|
991
|
+
"namespace": "identity",
|
|
992
|
+
"key": "name",
|
|
993
|
+
"value": "Charlie",
|
|
994
|
+
"rawEvidence": "I'm Charlie",
|
|
995
|
+
"confidence": 0.98,
|
|
996
|
+
"pii": true
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
"type": "identity",
|
|
1000
|
+
"namespace": "work",
|
|
1001
|
+
"key": "company",
|
|
1002
|
+
"value": "ZetaChain",
|
|
1003
|
+
"rawEvidence": "called ZetaChain",
|
|
1004
|
+
"confidence": 0.99,
|
|
1005
|
+
"pii": false
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
"type": "preference",
|
|
1009
|
+
"namespace": "answer_style",
|
|
1010
|
+
"key": "verbosity",
|
|
1011
|
+
"value": "concise_direct",
|
|
1012
|
+
"rawEvidence": "I prefer concise, direct answers",
|
|
1013
|
+
"confidence": 0.96,
|
|
1014
|
+
"pii": false
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
"type": "identity",
|
|
1018
|
+
"namespace": "timezone",
|
|
1019
|
+
"key": "tz",
|
|
1020
|
+
"value": "America/Los_Angeles",
|
|
1021
|
+
"rawEvidence": "I'm in PST",
|
|
1022
|
+
"confidence": 0.9,
|
|
1023
|
+
"pii": false
|
|
1024
|
+
}
|
|
1025
|
+
]
|
|
1026
|
+
}`;
|
|
1027
|
+
var preprocessMemories = (items, minConfidence = 0.6) => {
|
|
1028
|
+
if (!items || !Array.isArray(items)) {
|
|
1029
|
+
return [];
|
|
1030
|
+
}
|
|
1031
|
+
const validItems = items.filter((item) => {
|
|
1032
|
+
if (item.namespace == null || item.key == null || item.value == null) {
|
|
1033
|
+
console.warn(
|
|
1034
|
+
"Dropping memory item with null/undefined namespace, key, or value:",
|
|
1035
|
+
item
|
|
1036
|
+
);
|
|
1037
|
+
return false;
|
|
1020
1038
|
}
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1039
|
+
const namespace = String(item.namespace).trim();
|
|
1040
|
+
const key = String(item.key).trim();
|
|
1041
|
+
const value = String(item.value).trim();
|
|
1042
|
+
if (namespace === "" || key === "" || value === "") {
|
|
1043
|
+
console.warn(
|
|
1044
|
+
"Dropping memory item with empty namespace, key, or value after trimming:",
|
|
1045
|
+
item
|
|
1046
|
+
);
|
|
1047
|
+
return false;
|
|
1027
1048
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1049
|
+
if (typeof item.confidence !== "number" || item.confidence < minConfidence) {
|
|
1050
|
+
console.warn(
|
|
1051
|
+
`Dropping memory item with confidence ${item.confidence} below threshold ${minConfidence}:`,
|
|
1052
|
+
item
|
|
1053
|
+
);
|
|
1054
|
+
return false;
|
|
1031
1055
|
}
|
|
1032
|
-
return
|
|
1033
|
-
|
|
1034
|
-
|
|
1056
|
+
return true;
|
|
1057
|
+
});
|
|
1058
|
+
const deduplicatedMap = /* @__PURE__ */ new Map();
|
|
1059
|
+
for (const item of validItems) {
|
|
1060
|
+
const uniqueKey = `${item.namespace}:${item.key}:${item.value}`;
|
|
1061
|
+
const existing = deduplicatedMap.get(uniqueKey);
|
|
1062
|
+
if (!existing || item.confidence > existing.confidence) {
|
|
1063
|
+
deduplicatedMap.set(uniqueKey, item);
|
|
1064
|
+
} else {
|
|
1065
|
+
console.debug(
|
|
1066
|
+
`Deduplicating memory item: keeping entry with higher confidence (${existing.confidence} > ${item.confidence})`,
|
|
1067
|
+
{ namespace: item.namespace, key: item.key, value: item.value }
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return Array.from(deduplicatedMap.values());
|
|
1072
|
+
};
|
|
1073
|
+
|
|
1074
|
+
// src/lib/memory/db.ts
|
|
1075
|
+
import Dexie from "dexie";
|
|
1076
|
+
var MemoryDatabase = class extends Dexie {
|
|
1077
|
+
constructor() {
|
|
1078
|
+
super("MemoryDatabase");
|
|
1079
|
+
this.version(2).stores({
|
|
1080
|
+
memories: "++id, uniqueKey, compositeKey, namespace, key, type, createdAt, updatedAt"
|
|
1081
|
+
});
|
|
1082
|
+
this.version(3).stores({
|
|
1083
|
+
memories: "++id, uniqueKey, compositeKey, namespace, key, type, createdAt, updatedAt"
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
var memoryDb = new MemoryDatabase();
|
|
1088
|
+
var saveMemory = async (memory) => {
|
|
1089
|
+
const compositeKey = `${memory.namespace}:${memory.key}`;
|
|
1090
|
+
const uniqueKey = `${memory.namespace}:${memory.key}:${memory.value}`;
|
|
1091
|
+
const now = Date.now();
|
|
1092
|
+
const existing = await memoryDb.memories.where("uniqueKey").equals(uniqueKey).first();
|
|
1093
|
+
if (existing) {
|
|
1094
|
+
const shouldPreserveEmbedding = existing.value === memory.value && existing.rawEvidence === memory.rawEvidence && existing.type === memory.type && existing.namespace === memory.namespace && existing.key === memory.key && existing.embedding !== void 0 && existing.embedding.length > 0;
|
|
1095
|
+
const updateData = {
|
|
1096
|
+
...memory,
|
|
1097
|
+
compositeKey,
|
|
1098
|
+
uniqueKey,
|
|
1099
|
+
updatedAt: now,
|
|
1100
|
+
createdAt: existing.createdAt
|
|
1035
1101
|
};
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
};
|
|
1052
|
-
for (const fn of interceptors.request.fns) {
|
|
1053
|
-
if (fn) {
|
|
1054
|
-
await fn(requestInit);
|
|
1055
|
-
request2 = new Request(requestInit.url, requestInit);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
return request2;
|
|
1059
|
-
},
|
|
1060
|
-
url
|
|
1102
|
+
if (shouldPreserveEmbedding) {
|
|
1103
|
+
updateData.embedding = existing.embedding;
|
|
1104
|
+
updateData.embeddingModel = existing.embeddingModel;
|
|
1105
|
+
} else {
|
|
1106
|
+
updateData.embedding = [];
|
|
1107
|
+
updateData.embeddingModel = void 0;
|
|
1108
|
+
}
|
|
1109
|
+
await memoryDb.memories.update(existing.id, updateData);
|
|
1110
|
+
} else {
|
|
1111
|
+
await memoryDb.memories.add({
|
|
1112
|
+
...memory,
|
|
1113
|
+
compositeKey,
|
|
1114
|
+
uniqueKey,
|
|
1115
|
+
createdAt: now,
|
|
1116
|
+
updatedAt: now
|
|
1061
1117
|
});
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
var saveMemories = async (memories) => {
|
|
1121
|
+
await Promise.all(memories.map((memory) => saveMemory(memory)));
|
|
1122
|
+
};
|
|
1123
|
+
var getAllMemories = async () => {
|
|
1124
|
+
return memoryDb.memories.toArray();
|
|
1125
|
+
};
|
|
1126
|
+
var cosineSimilarity = (a, b) => {
|
|
1127
|
+
if (a.length !== b.length) {
|
|
1128
|
+
throw new Error("Vectors must have the same length");
|
|
1129
|
+
}
|
|
1130
|
+
let dotProduct = 0;
|
|
1131
|
+
let normA = 0;
|
|
1132
|
+
let normB = 0;
|
|
1133
|
+
for (let i = 0; i < a.length; i++) {
|
|
1134
|
+
dotProduct += a[i] * b[i];
|
|
1135
|
+
normA += a[i] * a[i];
|
|
1136
|
+
normB += b[i] * b[i];
|
|
1137
|
+
}
|
|
1138
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1139
|
+
if (denominator === 0) {
|
|
1140
|
+
return 0;
|
|
1141
|
+
}
|
|
1142
|
+
return dotProduct / denominator;
|
|
1143
|
+
};
|
|
1144
|
+
var searchSimilarMemories = async (queryEmbedding, limit = 10, minSimilarity = 0.6) => {
|
|
1145
|
+
const allMemories = await getAllMemories();
|
|
1146
|
+
const memoriesWithEmbeddings = allMemories.filter(
|
|
1147
|
+
(m) => m.embedding && m.embedding.length > 0
|
|
1148
|
+
);
|
|
1149
|
+
console.log(
|
|
1150
|
+
`[Memory Search] Total memories: ${allMemories.length}, memories with embeddings: ${memoriesWithEmbeddings.length}`
|
|
1151
|
+
);
|
|
1152
|
+
if (memoriesWithEmbeddings.length === 0) {
|
|
1153
|
+
console.warn(
|
|
1154
|
+
"[Memory Search] No memories with embeddings found. Memories may need embeddings generated. Use generateAndStoreEmbeddings() to generate embeddings for existing memories."
|
|
1155
|
+
);
|
|
1156
|
+
return [];
|
|
1157
|
+
}
|
|
1158
|
+
const allResults = memoriesWithEmbeddings.map((memory) => {
|
|
1159
|
+
const similarity = cosineSimilarity(queryEmbedding, memory.embedding);
|
|
1160
|
+
return {
|
|
1161
|
+
...memory,
|
|
1162
|
+
similarity
|
|
1163
|
+
};
|
|
1164
|
+
}).sort((a, b) => b.similarity - a.similarity);
|
|
1165
|
+
console.log(
|
|
1166
|
+
`[Memory Search] All similarity scores:`,
|
|
1167
|
+
allResults.map((r) => ({
|
|
1168
|
+
key: `${r.namespace}:${r.key}`,
|
|
1169
|
+
value: r.value,
|
|
1170
|
+
similarity: r.similarity.toFixed(4)
|
|
1171
|
+
}))
|
|
1172
|
+
);
|
|
1173
|
+
const results = allResults.filter((result) => result.similarity >= minSimilarity).slice(0, limit);
|
|
1174
|
+
if (results.length === 0 && allResults.length > 0) {
|
|
1175
|
+
const topSimilarity = allResults[0].similarity;
|
|
1176
|
+
const suggestedThreshold = Math.max(0.3, topSimilarity - 0.1);
|
|
1177
|
+
console.warn(
|
|
1178
|
+
`[Memory Search] No memories above threshold ${minSimilarity}. Highest similarity was ${topSimilarity.toFixed(4)}. Consider lowering the threshold to ${suggestedThreshold.toFixed(2)}`
|
|
1179
|
+
);
|
|
1180
|
+
} else {
|
|
1181
|
+
console.log(
|
|
1182
|
+
`[Memory Search] Found ${results.length} memories above similarity threshold ${minSimilarity}. Top similarity: ${results[0]?.similarity.toFixed(4) || "N/A"}`
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
return results;
|
|
1090
1186
|
};
|
|
1091
|
-
|
|
1092
|
-
// src/clientConfig.ts
|
|
1093
|
-
var createClientConfig = (config) => ({
|
|
1094
|
-
...config,
|
|
1095
|
-
baseUrl: "https://ai-portal-dev.zetachain.com"
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
// src/client/client.gen.ts
|
|
1099
|
-
var client = createClient(createClientConfig(createConfig()));
|
|
1100
1187
|
|
|
1101
1188
|
// src/client/sdk.gen.ts
|
|
1102
1189
|
var postApiV1Embeddings = (options) => {
|
|
@@ -1214,7 +1301,7 @@ function useMemory(options = {}) {
|
|
|
1214
1301
|
onFactsExtracted,
|
|
1215
1302
|
getToken
|
|
1216
1303
|
} = options;
|
|
1217
|
-
const extractionInProgressRef =
|
|
1304
|
+
const extractionInProgressRef = useRef2(false);
|
|
1218
1305
|
const extractMemoriesFromMessage = useCallback2(
|
|
1219
1306
|
async (options2) => {
|
|
1220
1307
|
const { messages, model } = options2;
|
|
@@ -1228,7 +1315,7 @@ function useMemory(options = {}) {
|
|
|
1228
1315
|
console.error("No access token available for memory extraction");
|
|
1229
1316
|
return null;
|
|
1230
1317
|
}
|
|
1231
|
-
const completion = await
|
|
1318
|
+
const completion = await postApiV1ChatCompletions({
|
|
1232
1319
|
body: {
|
|
1233
1320
|
messages: [
|
|
1234
1321
|
{
|