@reverbia/sdk 1.0.0-next.20251212012743 → 1.0.0-next.20251215143604
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 +1661 -20
- package/dist/expo/index.d.mts +748 -1
- package/dist/expo/index.d.ts +748 -1
- package/dist/expo/index.mjs +1641 -10
- package/dist/next/index.d.mts +2 -0
- package/dist/next/index.d.ts +2 -0
- package/dist/react/index.cjs +1377 -418
- package/dist/react/index.d.mts +702 -47
- package/dist/react/index.d.ts +702 -47
- package/dist/react/index.mjs +1348 -398
- package/package.json +7 -6
package/dist/expo/index.cjs
CHANGED
|
@@ -20,8 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/expo/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ChatConversation: () => Conversation,
|
|
24
|
+
ChatMessage: () => Message,
|
|
25
|
+
StoredMemoryModel: () => Memory,
|
|
26
|
+
chatStorageSchema: () => chatStorageSchema,
|
|
27
|
+
generateCompositeKey: () => generateCompositeKey,
|
|
28
|
+
generateConversationId: () => generateConversationId,
|
|
29
|
+
generateUniqueKey: () => generateUniqueKey,
|
|
30
|
+
memoryStorageSchema: () => memoryStorageSchema,
|
|
23
31
|
useChat: () => useChat,
|
|
32
|
+
useChatStorage: () => useChatStorage,
|
|
24
33
|
useImageGeneration: () => useImageGeneration,
|
|
34
|
+
useMemoryStorage: () => useMemoryStorage,
|
|
25
35
|
useModels: () => useModels
|
|
26
36
|
});
|
|
27
37
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -308,9 +318,405 @@ function useChat(options) {
|
|
|
308
318
|
};
|
|
309
319
|
}
|
|
310
320
|
|
|
311
|
-
// src/
|
|
321
|
+
// src/expo/useChatStorage.ts
|
|
312
322
|
var import_react2 = require("react");
|
|
313
323
|
|
|
324
|
+
// src/lib/chatStorage/types.ts
|
|
325
|
+
function convertUsageToStored(usage) {
|
|
326
|
+
if (!usage) return void 0;
|
|
327
|
+
return {
|
|
328
|
+
promptTokens: usage.prompt_tokens,
|
|
329
|
+
completionTokens: usage.completion_tokens,
|
|
330
|
+
totalTokens: usage.total_tokens,
|
|
331
|
+
costMicroUsd: usage.cost_micro_usd
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function generateConversationId() {
|
|
335
|
+
return `conv_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/lib/chatStorage/operations.ts
|
|
339
|
+
var import_watermelondb = require("@nozbe/watermelondb");
|
|
340
|
+
function messageToStored(message) {
|
|
341
|
+
return {
|
|
342
|
+
uniqueId: message.id,
|
|
343
|
+
messageId: message.messageId,
|
|
344
|
+
conversationId: message.conversationId,
|
|
345
|
+
role: message.role,
|
|
346
|
+
content: message.content,
|
|
347
|
+
model: message.model,
|
|
348
|
+
files: message.files,
|
|
349
|
+
createdAt: message.createdAt,
|
|
350
|
+
updatedAt: message.updatedAt,
|
|
351
|
+
vector: message.vector,
|
|
352
|
+
embeddingModel: message.embeddingModel,
|
|
353
|
+
usage: message.usage,
|
|
354
|
+
sources: message.sources,
|
|
355
|
+
responseDuration: message.responseDuration
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function conversationToStored(conversation) {
|
|
359
|
+
return {
|
|
360
|
+
uniqueId: conversation.id,
|
|
361
|
+
conversationId: conversation.conversationId,
|
|
362
|
+
title: conversation.title,
|
|
363
|
+
createdAt: conversation.createdAt,
|
|
364
|
+
updatedAt: conversation.updatedAt,
|
|
365
|
+
isDeleted: conversation.isDeleted
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async function createConversationOp(ctx, opts, defaultTitle = "New Conversation") {
|
|
369
|
+
const convId = opts?.conversationId || generateConversationId();
|
|
370
|
+
const title = opts?.title || defaultTitle;
|
|
371
|
+
const created = await ctx.database.write(async () => {
|
|
372
|
+
return await ctx.conversationsCollection.create((conv) => {
|
|
373
|
+
conv._setRaw("conversation_id", convId);
|
|
374
|
+
conv._setRaw("title", title);
|
|
375
|
+
conv._setRaw("is_deleted", false);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
return conversationToStored(created);
|
|
379
|
+
}
|
|
380
|
+
async function getConversationOp(ctx, id) {
|
|
381
|
+
const results = await ctx.conversationsCollection.query(import_watermelondb.Q.where("conversation_id", id), import_watermelondb.Q.where("is_deleted", false)).fetch();
|
|
382
|
+
return results.length > 0 ? conversationToStored(results[0]) : null;
|
|
383
|
+
}
|
|
384
|
+
async function getConversationsOp(ctx) {
|
|
385
|
+
const results = await ctx.conversationsCollection.query(import_watermelondb.Q.where("is_deleted", false), import_watermelondb.Q.sortBy("created_at", import_watermelondb.Q.desc)).fetch();
|
|
386
|
+
return results.map(conversationToStored);
|
|
387
|
+
}
|
|
388
|
+
async function updateConversationTitleOp(ctx, id, title) {
|
|
389
|
+
const results = await ctx.conversationsCollection.query(import_watermelondb.Q.where("conversation_id", id), import_watermelondb.Q.where("is_deleted", false)).fetch();
|
|
390
|
+
if (results.length > 0) {
|
|
391
|
+
await ctx.database.write(async () => {
|
|
392
|
+
await results[0].update((conv) => {
|
|
393
|
+
conv._setRaw("title", title);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
async function deleteConversationOp(ctx, id) {
|
|
401
|
+
const results = await ctx.conversationsCollection.query(import_watermelondb.Q.where("conversation_id", id), import_watermelondb.Q.where("is_deleted", false)).fetch();
|
|
402
|
+
if (results.length > 0) {
|
|
403
|
+
await ctx.database.write(async () => {
|
|
404
|
+
await results[0].update((conv) => {
|
|
405
|
+
conv._setRaw("is_deleted", true);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
async function getMessagesOp(ctx, convId) {
|
|
413
|
+
const results = await ctx.messagesCollection.query(import_watermelondb.Q.where("conversation_id", convId), import_watermelondb.Q.sortBy("message_id", import_watermelondb.Q.asc)).fetch();
|
|
414
|
+
return results.map(messageToStored);
|
|
415
|
+
}
|
|
416
|
+
async function getMessageCountOp(ctx, convId) {
|
|
417
|
+
return await ctx.messagesCollection.query(import_watermelondb.Q.where("conversation_id", convId)).fetchCount();
|
|
418
|
+
}
|
|
419
|
+
async function clearMessagesOp(ctx, convId) {
|
|
420
|
+
const messages = await ctx.messagesCollection.query(import_watermelondb.Q.where("conversation_id", convId)).fetch();
|
|
421
|
+
await ctx.database.write(async () => {
|
|
422
|
+
for (const message of messages) {
|
|
423
|
+
await message.destroyPermanently();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
async function createMessageOp(ctx, opts) {
|
|
428
|
+
const existingCount = await getMessageCountOp(ctx, opts.conversationId);
|
|
429
|
+
const messageId = existingCount + 1;
|
|
430
|
+
const created = await ctx.database.write(async () => {
|
|
431
|
+
return await ctx.messagesCollection.create((msg) => {
|
|
432
|
+
msg._setRaw("message_id", messageId);
|
|
433
|
+
msg._setRaw("conversation_id", opts.conversationId);
|
|
434
|
+
msg._setRaw("role", opts.role);
|
|
435
|
+
msg._setRaw("content", opts.content);
|
|
436
|
+
if (opts.model) msg._setRaw("model", opts.model);
|
|
437
|
+
if (opts.files) msg._setRaw("files", JSON.stringify(opts.files));
|
|
438
|
+
if (opts.usage) msg._setRaw("usage", JSON.stringify(opts.usage));
|
|
439
|
+
if (opts.sources) msg._setRaw("sources", JSON.stringify(opts.sources));
|
|
440
|
+
if (opts.responseDuration !== void 0)
|
|
441
|
+
msg._setRaw("response_duration", opts.responseDuration);
|
|
442
|
+
if (opts.vector) msg._setRaw("vector", JSON.stringify(opts.vector));
|
|
443
|
+
if (opts.embeddingModel) msg._setRaw("embedding_model", opts.embeddingModel);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
return messageToStored(created);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/expo/useChatStorage.ts
|
|
450
|
+
function storedToLlmapiMessage(stored) {
|
|
451
|
+
const content = [
|
|
452
|
+
{ type: "text", text: stored.content }
|
|
453
|
+
];
|
|
454
|
+
if (stored.files?.length) {
|
|
455
|
+
for (const file of stored.files) {
|
|
456
|
+
if (file.url) {
|
|
457
|
+
content.push({
|
|
458
|
+
type: "image_url",
|
|
459
|
+
image_url: { url: file.url }
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
role: stored.role,
|
|
466
|
+
content
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function useChatStorage(options) {
|
|
470
|
+
const {
|
|
471
|
+
database,
|
|
472
|
+
conversationId: initialConversationId,
|
|
473
|
+
autoCreateConversation = true,
|
|
474
|
+
defaultConversationTitle = "New Conversation",
|
|
475
|
+
getToken,
|
|
476
|
+
baseUrl,
|
|
477
|
+
onData,
|
|
478
|
+
onFinish,
|
|
479
|
+
onError
|
|
480
|
+
} = options;
|
|
481
|
+
const [currentConversationId, setCurrentConversationId] = (0, import_react2.useState)(initialConversationId || null);
|
|
482
|
+
const messagesCollection = (0, import_react2.useMemo)(
|
|
483
|
+
() => database.get("history"),
|
|
484
|
+
[database]
|
|
485
|
+
);
|
|
486
|
+
const conversationsCollection = (0, import_react2.useMemo)(
|
|
487
|
+
() => database.get("conversations"),
|
|
488
|
+
[database]
|
|
489
|
+
);
|
|
490
|
+
const storageCtx = (0, import_react2.useMemo)(
|
|
491
|
+
() => ({
|
|
492
|
+
database,
|
|
493
|
+
messagesCollection,
|
|
494
|
+
conversationsCollection
|
|
495
|
+
}),
|
|
496
|
+
[database, messagesCollection, conversationsCollection]
|
|
497
|
+
);
|
|
498
|
+
const {
|
|
499
|
+
isLoading,
|
|
500
|
+
sendMessage: baseSendMessage,
|
|
501
|
+
stop
|
|
502
|
+
} = useChat({
|
|
503
|
+
getToken,
|
|
504
|
+
baseUrl,
|
|
505
|
+
onData,
|
|
506
|
+
onFinish,
|
|
507
|
+
onError
|
|
508
|
+
});
|
|
509
|
+
const createConversation = (0, import_react2.useCallback)(
|
|
510
|
+
async (opts) => {
|
|
511
|
+
const created = await createConversationOp(
|
|
512
|
+
storageCtx,
|
|
513
|
+
opts,
|
|
514
|
+
defaultConversationTitle
|
|
515
|
+
);
|
|
516
|
+
setCurrentConversationId(created.conversationId);
|
|
517
|
+
return created;
|
|
518
|
+
},
|
|
519
|
+
[storageCtx, defaultConversationTitle]
|
|
520
|
+
);
|
|
521
|
+
const getConversation = (0, import_react2.useCallback)(
|
|
522
|
+
async (id) => {
|
|
523
|
+
return getConversationOp(storageCtx, id);
|
|
524
|
+
},
|
|
525
|
+
[storageCtx]
|
|
526
|
+
);
|
|
527
|
+
const getConversations = (0, import_react2.useCallback)(async () => {
|
|
528
|
+
return getConversationsOp(storageCtx);
|
|
529
|
+
}, [storageCtx]);
|
|
530
|
+
const updateConversationTitle = (0, import_react2.useCallback)(
|
|
531
|
+
async (id, title) => {
|
|
532
|
+
return updateConversationTitleOp(storageCtx, id, title);
|
|
533
|
+
},
|
|
534
|
+
[storageCtx]
|
|
535
|
+
);
|
|
536
|
+
const deleteConversation = (0, import_react2.useCallback)(
|
|
537
|
+
async (id) => {
|
|
538
|
+
const deleted = await deleteConversationOp(storageCtx, id);
|
|
539
|
+
if (deleted && currentConversationId === id) {
|
|
540
|
+
setCurrentConversationId(null);
|
|
541
|
+
}
|
|
542
|
+
return deleted;
|
|
543
|
+
},
|
|
544
|
+
[storageCtx, currentConversationId]
|
|
545
|
+
);
|
|
546
|
+
const getMessages = (0, import_react2.useCallback)(
|
|
547
|
+
async (convId) => {
|
|
548
|
+
return getMessagesOp(storageCtx, convId);
|
|
549
|
+
},
|
|
550
|
+
[storageCtx]
|
|
551
|
+
);
|
|
552
|
+
const getMessageCount = (0, import_react2.useCallback)(
|
|
553
|
+
async (convId) => {
|
|
554
|
+
return getMessageCountOp(storageCtx, convId);
|
|
555
|
+
},
|
|
556
|
+
[storageCtx]
|
|
557
|
+
);
|
|
558
|
+
const clearMessages = (0, import_react2.useCallback)(
|
|
559
|
+
async (convId) => {
|
|
560
|
+
return clearMessagesOp(storageCtx, convId);
|
|
561
|
+
},
|
|
562
|
+
[storageCtx]
|
|
563
|
+
);
|
|
564
|
+
const ensureConversation = (0, import_react2.useCallback)(async () => {
|
|
565
|
+
if (currentConversationId) {
|
|
566
|
+
const existing = await getConversation(currentConversationId);
|
|
567
|
+
if (existing) {
|
|
568
|
+
return currentConversationId;
|
|
569
|
+
}
|
|
570
|
+
if (autoCreateConversation) {
|
|
571
|
+
const newConv = await createConversation({
|
|
572
|
+
conversationId: currentConversationId
|
|
573
|
+
});
|
|
574
|
+
return newConv.conversationId;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (autoCreateConversation) {
|
|
578
|
+
const newConv = await createConversation();
|
|
579
|
+
return newConv.conversationId;
|
|
580
|
+
}
|
|
581
|
+
throw new Error(
|
|
582
|
+
"No conversation ID provided and autoCreateConversation is disabled"
|
|
583
|
+
);
|
|
584
|
+
}, [
|
|
585
|
+
currentConversationId,
|
|
586
|
+
getConversation,
|
|
587
|
+
autoCreateConversation,
|
|
588
|
+
createConversation
|
|
589
|
+
]);
|
|
590
|
+
const sendMessage = (0, import_react2.useCallback)(
|
|
591
|
+
async (args) => {
|
|
592
|
+
const {
|
|
593
|
+
content,
|
|
594
|
+
model,
|
|
595
|
+
messages: providedMessages,
|
|
596
|
+
includeHistory = true,
|
|
597
|
+
maxHistoryMessages = 50,
|
|
598
|
+
files,
|
|
599
|
+
onData: perRequestOnData
|
|
600
|
+
} = args;
|
|
601
|
+
let convId;
|
|
602
|
+
try {
|
|
603
|
+
convId = await ensureConversation();
|
|
604
|
+
} catch (err) {
|
|
605
|
+
return {
|
|
606
|
+
data: null,
|
|
607
|
+
error: err instanceof Error ? err.message : "Failed to ensure conversation"
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
let messagesToSend = [];
|
|
611
|
+
if (includeHistory && !providedMessages) {
|
|
612
|
+
const storedMessages = await getMessages(convId);
|
|
613
|
+
const limitedMessages = storedMessages.slice(-maxHistoryMessages);
|
|
614
|
+
messagesToSend = limitedMessages.map(storedToLlmapiMessage);
|
|
615
|
+
} else if (providedMessages) {
|
|
616
|
+
messagesToSend = providedMessages;
|
|
617
|
+
}
|
|
618
|
+
const userMessageContent = [
|
|
619
|
+
{ type: "text", text: content }
|
|
620
|
+
];
|
|
621
|
+
if (files?.length) {
|
|
622
|
+
for (const file of files) {
|
|
623
|
+
if (file.url) {
|
|
624
|
+
userMessageContent.push({
|
|
625
|
+
type: "image_url",
|
|
626
|
+
image_url: { url: file.url }
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const userMessage = {
|
|
632
|
+
role: "user",
|
|
633
|
+
content: userMessageContent
|
|
634
|
+
};
|
|
635
|
+
messagesToSend.push(userMessage);
|
|
636
|
+
const sanitizedFiles = files?.map((file) => ({
|
|
637
|
+
id: file.id,
|
|
638
|
+
name: file.name,
|
|
639
|
+
type: file.type,
|
|
640
|
+
size: file.size,
|
|
641
|
+
// Only keep URL if it's not a data URI (e.g., external URLs)
|
|
642
|
+
url: file.url && !file.url.startsWith("data:") ? file.url : void 0
|
|
643
|
+
}));
|
|
644
|
+
let storedUserMessage;
|
|
645
|
+
try {
|
|
646
|
+
storedUserMessage = await createMessageOp(storageCtx, {
|
|
647
|
+
conversationId: convId,
|
|
648
|
+
role: "user",
|
|
649
|
+
content,
|
|
650
|
+
files: sanitizedFiles
|
|
651
|
+
});
|
|
652
|
+
} catch (err) {
|
|
653
|
+
return {
|
|
654
|
+
data: null,
|
|
655
|
+
error: err instanceof Error ? err.message : "Failed to store user message"
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
const startTime = Date.now();
|
|
659
|
+
const result = await baseSendMessage({
|
|
660
|
+
messages: messagesToSend,
|
|
661
|
+
model,
|
|
662
|
+
onData: perRequestOnData
|
|
663
|
+
});
|
|
664
|
+
const responseDuration = (Date.now() - startTime) / 1e3;
|
|
665
|
+
if (result.error || !result.data) {
|
|
666
|
+
return {
|
|
667
|
+
data: null,
|
|
668
|
+
error: result.error || "No response data received",
|
|
669
|
+
userMessage: storedUserMessage
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
const responseData = result.data;
|
|
673
|
+
const assistantContent = responseData.choices?.[0]?.message?.content?.map((part) => part.text || "").join("") || "";
|
|
674
|
+
let storedAssistantMessage;
|
|
675
|
+
try {
|
|
676
|
+
storedAssistantMessage = await createMessageOp(storageCtx, {
|
|
677
|
+
conversationId: convId,
|
|
678
|
+
role: "assistant",
|
|
679
|
+
content: assistantContent,
|
|
680
|
+
model: responseData.model,
|
|
681
|
+
usage: convertUsageToStored(responseData.usage),
|
|
682
|
+
responseDuration
|
|
683
|
+
});
|
|
684
|
+
} catch (err) {
|
|
685
|
+
return {
|
|
686
|
+
data: null,
|
|
687
|
+
error: err instanceof Error ? err.message : "Failed to store assistant message",
|
|
688
|
+
userMessage: storedUserMessage
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
data: responseData,
|
|
693
|
+
error: null,
|
|
694
|
+
userMessage: storedUserMessage,
|
|
695
|
+
assistantMessage: storedAssistantMessage
|
|
696
|
+
};
|
|
697
|
+
},
|
|
698
|
+
[ensureConversation, getMessages, storageCtx, baseSendMessage]
|
|
699
|
+
);
|
|
700
|
+
return {
|
|
701
|
+
isLoading,
|
|
702
|
+
sendMessage,
|
|
703
|
+
stop,
|
|
704
|
+
conversationId: currentConversationId,
|
|
705
|
+
setConversationId: setCurrentConversationId,
|
|
706
|
+
createConversation,
|
|
707
|
+
getConversation,
|
|
708
|
+
getConversations,
|
|
709
|
+
updateConversationTitle,
|
|
710
|
+
deleteConversation,
|
|
711
|
+
getMessages,
|
|
712
|
+
getMessageCount,
|
|
713
|
+
clearMessages
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/react/useImageGeneration.ts
|
|
718
|
+
var import_react3 = require("react");
|
|
719
|
+
|
|
314
720
|
// src/client/core/bodySerializer.gen.ts
|
|
315
721
|
var jsonBodySerializer = {
|
|
316
722
|
bodySerializer: (body) => JSON.stringify(
|
|
@@ -1140,9 +1546,9 @@ var getApiV1Models = (options) => {
|
|
|
1140
1546
|
// src/react/useImageGeneration.ts
|
|
1141
1547
|
function useImageGeneration(options = {}) {
|
|
1142
1548
|
const { getToken, baseUrl = BASE_URL, onFinish, onError } = options;
|
|
1143
|
-
const [isLoading, setIsLoading] = (0,
|
|
1144
|
-
const abortControllerRef = (0,
|
|
1145
|
-
(0,
|
|
1549
|
+
const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
|
|
1550
|
+
const abortControllerRef = (0, import_react3.useRef)(null);
|
|
1551
|
+
(0, import_react3.useEffect)(() => {
|
|
1146
1552
|
return () => {
|
|
1147
1553
|
if (abortControllerRef.current) {
|
|
1148
1554
|
abortControllerRef.current.abort();
|
|
@@ -1150,13 +1556,13 @@ function useImageGeneration(options = {}) {
|
|
|
1150
1556
|
}
|
|
1151
1557
|
};
|
|
1152
1558
|
}, []);
|
|
1153
|
-
const stop = (0,
|
|
1559
|
+
const stop = (0, import_react3.useCallback)(() => {
|
|
1154
1560
|
if (abortControllerRef.current) {
|
|
1155
1561
|
abortControllerRef.current.abort();
|
|
1156
1562
|
abortControllerRef.current = null;
|
|
1157
1563
|
}
|
|
1158
1564
|
}, []);
|
|
1159
|
-
const generateImage = (0,
|
|
1565
|
+
const generateImage = (0, import_react3.useCallback)(
|
|
1160
1566
|
async (args) => {
|
|
1161
1567
|
if (abortControllerRef.current) {
|
|
1162
1568
|
abortControllerRef.current.abort();
|
|
@@ -1219,22 +1625,22 @@ function useImageGeneration(options = {}) {
|
|
|
1219
1625
|
}
|
|
1220
1626
|
|
|
1221
1627
|
// src/react/useModels.ts
|
|
1222
|
-
var
|
|
1628
|
+
var import_react4 = require("react");
|
|
1223
1629
|
function useModels(options = {}) {
|
|
1224
1630
|
const { getToken, baseUrl = BASE_URL, provider, autoFetch = true } = options;
|
|
1225
|
-
const [models, setModels] = (0,
|
|
1226
|
-
const [isLoading, setIsLoading] = (0,
|
|
1227
|
-
const [error, setError] = (0,
|
|
1228
|
-
const getTokenRef = (0,
|
|
1229
|
-
const baseUrlRef = (0,
|
|
1230
|
-
const providerRef = (0,
|
|
1231
|
-
const abortControllerRef = (0,
|
|
1232
|
-
(0,
|
|
1631
|
+
const [models, setModels] = (0, import_react4.useState)([]);
|
|
1632
|
+
const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
|
|
1633
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
1634
|
+
const getTokenRef = (0, import_react4.useRef)(getToken);
|
|
1635
|
+
const baseUrlRef = (0, import_react4.useRef)(baseUrl);
|
|
1636
|
+
const providerRef = (0, import_react4.useRef)(provider);
|
|
1637
|
+
const abortControllerRef = (0, import_react4.useRef)(null);
|
|
1638
|
+
(0, import_react4.useEffect)(() => {
|
|
1233
1639
|
getTokenRef.current = getToken;
|
|
1234
1640
|
baseUrlRef.current = baseUrl;
|
|
1235
1641
|
providerRef.current = provider;
|
|
1236
1642
|
});
|
|
1237
|
-
(0,
|
|
1643
|
+
(0, import_react4.useEffect)(() => {
|
|
1238
1644
|
return () => {
|
|
1239
1645
|
if (abortControllerRef.current) {
|
|
1240
1646
|
abortControllerRef.current.abort();
|
|
@@ -1242,7 +1648,7 @@ function useModels(options = {}) {
|
|
|
1242
1648
|
}
|
|
1243
1649
|
};
|
|
1244
1650
|
}, []);
|
|
1245
|
-
const fetchModels = (0,
|
|
1651
|
+
const fetchModels = (0, import_react4.useCallback)(async () => {
|
|
1246
1652
|
if (abortControllerRef.current) {
|
|
1247
1653
|
abortControllerRef.current.abort();
|
|
1248
1654
|
}
|
|
@@ -1300,12 +1706,12 @@ function useModels(options = {}) {
|
|
|
1300
1706
|
}
|
|
1301
1707
|
}
|
|
1302
1708
|
}, []);
|
|
1303
|
-
const refetch = (0,
|
|
1709
|
+
const refetch = (0, import_react4.useCallback)(async () => {
|
|
1304
1710
|
setModels([]);
|
|
1305
1711
|
await fetchModels();
|
|
1306
1712
|
}, [fetchModels]);
|
|
1307
|
-
const hasFetchedRef = (0,
|
|
1308
|
-
(0,
|
|
1713
|
+
const hasFetchedRef = (0, import_react4.useRef)(false);
|
|
1714
|
+
(0, import_react4.useEffect)(() => {
|
|
1309
1715
|
if (autoFetch && !hasFetchedRef.current) {
|
|
1310
1716
|
hasFetchedRef.current = true;
|
|
1311
1717
|
fetchModels();
|
|
@@ -1321,9 +1727,1244 @@ function useModels(options = {}) {
|
|
|
1321
1727
|
refetch
|
|
1322
1728
|
};
|
|
1323
1729
|
}
|
|
1730
|
+
|
|
1731
|
+
// src/expo/useMemoryStorage.ts
|
|
1732
|
+
var import_react5 = require("react");
|
|
1733
|
+
var import_client4 = require("@reverbia/sdk");
|
|
1734
|
+
|
|
1735
|
+
// src/lib/memoryStorage/operations.ts
|
|
1736
|
+
var import_watermelondb2 = require("@nozbe/watermelondb");
|
|
1737
|
+
|
|
1738
|
+
// src/lib/memoryStorage/types.ts
|
|
1739
|
+
function generateCompositeKey(namespace, key) {
|
|
1740
|
+
return `${namespace}:${key}`;
|
|
1741
|
+
}
|
|
1742
|
+
function generateUniqueKey(namespace, key, value) {
|
|
1743
|
+
return `${namespace}:${key}:${value}`;
|
|
1744
|
+
}
|
|
1745
|
+
function cosineSimilarity(a, b) {
|
|
1746
|
+
if (a.length !== b.length) {
|
|
1747
|
+
throw new Error("Vectors must have the same length");
|
|
1748
|
+
}
|
|
1749
|
+
let dotProduct = 0;
|
|
1750
|
+
let normA = 0;
|
|
1751
|
+
let normB = 0;
|
|
1752
|
+
for (let i = 0; i < a.length; i++) {
|
|
1753
|
+
dotProduct += a[i] * b[i];
|
|
1754
|
+
normA += a[i] * a[i];
|
|
1755
|
+
normB += b[i] * b[i];
|
|
1756
|
+
}
|
|
1757
|
+
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1758
|
+
if (denominator === 0) {
|
|
1759
|
+
return 0;
|
|
1760
|
+
}
|
|
1761
|
+
return dotProduct / denominator;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// src/lib/memoryStorage/operations.ts
|
|
1765
|
+
function memoryToStored(memory) {
|
|
1766
|
+
return {
|
|
1767
|
+
uniqueId: memory.id,
|
|
1768
|
+
type: memory.type,
|
|
1769
|
+
namespace: memory.namespace,
|
|
1770
|
+
key: memory.key,
|
|
1771
|
+
value: memory.value,
|
|
1772
|
+
rawEvidence: memory.rawEvidence,
|
|
1773
|
+
confidence: memory.confidence,
|
|
1774
|
+
pii: memory.pii,
|
|
1775
|
+
compositeKey: memory.compositeKey,
|
|
1776
|
+
uniqueKey: memory.uniqueKey,
|
|
1777
|
+
createdAt: memory.createdAt,
|
|
1778
|
+
updatedAt: memory.updatedAt,
|
|
1779
|
+
embedding: memory.embedding,
|
|
1780
|
+
embeddingModel: memory.embeddingModel,
|
|
1781
|
+
isDeleted: memory.isDeleted
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
async function getAllMemoriesOp(ctx) {
|
|
1785
|
+
const results = await ctx.memoriesCollection.query(import_watermelondb2.Q.where("is_deleted", false), import_watermelondb2.Q.sortBy("created_at", import_watermelondb2.Q.desc)).fetch();
|
|
1786
|
+
return results.map(memoryToStored);
|
|
1787
|
+
}
|
|
1788
|
+
async function getMemoryByIdOp(ctx, id) {
|
|
1789
|
+
try {
|
|
1790
|
+
const memory = await ctx.memoriesCollection.find(id);
|
|
1791
|
+
if (memory.isDeleted) return null;
|
|
1792
|
+
return memoryToStored(memory);
|
|
1793
|
+
} catch {
|
|
1794
|
+
return null;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
async function getMemoriesByNamespaceOp(ctx, namespace) {
|
|
1798
|
+
const results = await ctx.memoriesCollection.query(
|
|
1799
|
+
import_watermelondb2.Q.where("namespace", namespace),
|
|
1800
|
+
import_watermelondb2.Q.where("is_deleted", false),
|
|
1801
|
+
import_watermelondb2.Q.sortBy("created_at", import_watermelondb2.Q.desc)
|
|
1802
|
+
).fetch();
|
|
1803
|
+
return results.map(memoryToStored);
|
|
1804
|
+
}
|
|
1805
|
+
async function getMemoriesByKeyOp(ctx, namespace, key) {
|
|
1806
|
+
const compositeKey = generateCompositeKey(namespace, key);
|
|
1807
|
+
const results = await ctx.memoriesCollection.query(
|
|
1808
|
+
import_watermelondb2.Q.where("composite_key", compositeKey),
|
|
1809
|
+
import_watermelondb2.Q.where("is_deleted", false),
|
|
1810
|
+
import_watermelondb2.Q.sortBy("created_at", import_watermelondb2.Q.desc)
|
|
1811
|
+
).fetch();
|
|
1812
|
+
return results.map(memoryToStored);
|
|
1813
|
+
}
|
|
1814
|
+
async function saveMemoryOp(ctx, opts) {
|
|
1815
|
+
const compositeKey = generateCompositeKey(opts.namespace, opts.key);
|
|
1816
|
+
const uniqueKey = generateUniqueKey(opts.namespace, opts.key, opts.value);
|
|
1817
|
+
const result = await ctx.database.write(async () => {
|
|
1818
|
+
const existing = await ctx.memoriesCollection.query(import_watermelondb2.Q.where("unique_key", uniqueKey)).fetch();
|
|
1819
|
+
if (existing.length > 0) {
|
|
1820
|
+
const existingMemory = existing[0];
|
|
1821
|
+
const shouldPreserveEmbedding = existingMemory.value === opts.value && existingMemory.rawEvidence === opts.rawEvidence && existingMemory.type === opts.type && existingMemory.namespace === opts.namespace && existingMemory.key === opts.key && existingMemory.embedding !== void 0 && existingMemory.embedding.length > 0 && !opts.embedding;
|
|
1822
|
+
await existingMemory.update((mem) => {
|
|
1823
|
+
mem._setRaw("type", opts.type);
|
|
1824
|
+
mem._setRaw("namespace", opts.namespace);
|
|
1825
|
+
mem._setRaw("key", opts.key);
|
|
1826
|
+
mem._setRaw("value", opts.value);
|
|
1827
|
+
mem._setRaw("raw_evidence", opts.rawEvidence);
|
|
1828
|
+
mem._setRaw("confidence", opts.confidence);
|
|
1829
|
+
mem._setRaw("pii", opts.pii);
|
|
1830
|
+
mem._setRaw("composite_key", compositeKey);
|
|
1831
|
+
mem._setRaw("unique_key", uniqueKey);
|
|
1832
|
+
mem._setRaw("is_deleted", false);
|
|
1833
|
+
if (shouldPreserveEmbedding) {
|
|
1834
|
+
} else if (opts.embedding) {
|
|
1835
|
+
mem._setRaw("embedding", JSON.stringify(opts.embedding));
|
|
1836
|
+
if (opts.embeddingModel) {
|
|
1837
|
+
mem._setRaw("embedding_model", opts.embeddingModel);
|
|
1838
|
+
}
|
|
1839
|
+
} else {
|
|
1840
|
+
mem._setRaw("embedding", null);
|
|
1841
|
+
mem._setRaw("embedding_model", null);
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1844
|
+
return existingMemory;
|
|
1845
|
+
}
|
|
1846
|
+
return await ctx.memoriesCollection.create((mem) => {
|
|
1847
|
+
mem._setRaw("type", opts.type);
|
|
1848
|
+
mem._setRaw("namespace", opts.namespace);
|
|
1849
|
+
mem._setRaw("key", opts.key);
|
|
1850
|
+
mem._setRaw("value", opts.value);
|
|
1851
|
+
mem._setRaw("raw_evidence", opts.rawEvidence);
|
|
1852
|
+
mem._setRaw("confidence", opts.confidence);
|
|
1853
|
+
mem._setRaw("pii", opts.pii);
|
|
1854
|
+
mem._setRaw("composite_key", compositeKey);
|
|
1855
|
+
mem._setRaw("unique_key", uniqueKey);
|
|
1856
|
+
mem._setRaw("is_deleted", false);
|
|
1857
|
+
if (opts.embedding) {
|
|
1858
|
+
mem._setRaw("embedding", JSON.stringify(opts.embedding));
|
|
1859
|
+
if (opts.embeddingModel) {
|
|
1860
|
+
mem._setRaw("embedding_model", opts.embeddingModel);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
});
|
|
1864
|
+
});
|
|
1865
|
+
return memoryToStored(result);
|
|
1866
|
+
}
|
|
1867
|
+
async function saveMemoriesOp(ctx, memories) {
|
|
1868
|
+
const results = [];
|
|
1869
|
+
for (const memory of memories) {
|
|
1870
|
+
const saved = await saveMemoryOp(ctx, memory);
|
|
1871
|
+
results.push(saved);
|
|
1872
|
+
}
|
|
1873
|
+
return results;
|
|
1874
|
+
}
|
|
1875
|
+
async function updateMemoryOp(ctx, id, updates) {
|
|
1876
|
+
let memory;
|
|
1877
|
+
try {
|
|
1878
|
+
memory = await ctx.memoriesCollection.find(id);
|
|
1879
|
+
} catch {
|
|
1880
|
+
return { ok: false, reason: "not_found" };
|
|
1881
|
+
}
|
|
1882
|
+
if (memory.isDeleted) {
|
|
1883
|
+
return { ok: false, reason: "not_found" };
|
|
1884
|
+
}
|
|
1885
|
+
const newNamespace = updates.namespace ?? memory.namespace;
|
|
1886
|
+
const newKey = updates.key ?? memory.key;
|
|
1887
|
+
const newValue = updates.value ?? memory.value;
|
|
1888
|
+
const newCompositeKey = generateCompositeKey(newNamespace, newKey);
|
|
1889
|
+
const newUniqueKey = generateUniqueKey(newNamespace, newKey, newValue);
|
|
1890
|
+
if (newUniqueKey !== memory.uniqueKey) {
|
|
1891
|
+
const existing = await ctx.memoriesCollection.query(import_watermelondb2.Q.where("unique_key", newUniqueKey), import_watermelondb2.Q.where("is_deleted", false)).fetch();
|
|
1892
|
+
if (existing.length > 0) {
|
|
1893
|
+
return { ok: false, reason: "conflict", conflictingKey: newUniqueKey };
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
try {
|
|
1897
|
+
const updated = await ctx.database.write(async () => {
|
|
1898
|
+
await memory.update((mem) => {
|
|
1899
|
+
if (updates.type !== void 0) mem._setRaw("type", updates.type);
|
|
1900
|
+
if (updates.namespace !== void 0)
|
|
1901
|
+
mem._setRaw("namespace", updates.namespace);
|
|
1902
|
+
if (updates.key !== void 0) mem._setRaw("key", updates.key);
|
|
1903
|
+
if (updates.value !== void 0) mem._setRaw("value", updates.value);
|
|
1904
|
+
if (updates.rawEvidence !== void 0)
|
|
1905
|
+
mem._setRaw("raw_evidence", updates.rawEvidence);
|
|
1906
|
+
if (updates.confidence !== void 0)
|
|
1907
|
+
mem._setRaw("confidence", updates.confidence);
|
|
1908
|
+
if (updates.pii !== void 0) mem._setRaw("pii", updates.pii);
|
|
1909
|
+
if (updates.namespace !== void 0 || updates.key !== void 0 || updates.value !== void 0) {
|
|
1910
|
+
mem._setRaw("composite_key", newCompositeKey);
|
|
1911
|
+
mem._setRaw("unique_key", newUniqueKey);
|
|
1912
|
+
}
|
|
1913
|
+
if (updates.embedding !== void 0) {
|
|
1914
|
+
mem._setRaw(
|
|
1915
|
+
"embedding",
|
|
1916
|
+
updates.embedding ? JSON.stringify(updates.embedding) : null
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
if (updates.embeddingModel !== void 0) {
|
|
1920
|
+
mem._setRaw("embedding_model", updates.embeddingModel || null);
|
|
1921
|
+
}
|
|
1922
|
+
});
|
|
1923
|
+
return memory;
|
|
1924
|
+
});
|
|
1925
|
+
return { ok: true, memory: memoryToStored(updated) };
|
|
1926
|
+
} catch (err) {
|
|
1927
|
+
return {
|
|
1928
|
+
ok: false,
|
|
1929
|
+
reason: "error",
|
|
1930
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
async function deleteMemoryByIdOp(ctx, id) {
|
|
1935
|
+
try {
|
|
1936
|
+
const memory = await ctx.memoriesCollection.find(id);
|
|
1937
|
+
await ctx.database.write(async () => {
|
|
1938
|
+
await memory.update((mem) => {
|
|
1939
|
+
mem._setRaw("is_deleted", true);
|
|
1940
|
+
});
|
|
1941
|
+
});
|
|
1942
|
+
} catch {
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
async function deleteMemoryOp(ctx, namespace, key, value) {
|
|
1946
|
+
const uniqueKey = generateUniqueKey(namespace, key, value);
|
|
1947
|
+
const results = await ctx.memoriesCollection.query(import_watermelondb2.Q.where("unique_key", uniqueKey)).fetch();
|
|
1948
|
+
if (results.length > 0) {
|
|
1949
|
+
await ctx.database.write(async () => {
|
|
1950
|
+
await results[0].update((mem) => {
|
|
1951
|
+
mem._setRaw("is_deleted", true);
|
|
1952
|
+
});
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
async function deleteMemoriesByKeyOp(ctx, namespace, key) {
|
|
1957
|
+
const compositeKey = generateCompositeKey(namespace, key);
|
|
1958
|
+
const results = await ctx.memoriesCollection.query(import_watermelondb2.Q.where("composite_key", compositeKey), import_watermelondb2.Q.where("is_deleted", false)).fetch();
|
|
1959
|
+
await ctx.database.write(async () => {
|
|
1960
|
+
for (const memory of results) {
|
|
1961
|
+
await memory.update((mem) => {
|
|
1962
|
+
mem._setRaw("is_deleted", true);
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
async function clearAllMemoriesOp(ctx) {
|
|
1968
|
+
const results = await ctx.memoriesCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
|
|
1969
|
+
await ctx.database.write(async () => {
|
|
1970
|
+
for (const memory of results) {
|
|
1971
|
+
await memory.update((mem) => {
|
|
1972
|
+
mem._setRaw("is_deleted", true);
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
async function searchSimilarMemoriesOp(ctx, queryEmbedding, limit = 10, minSimilarity = 0.6) {
|
|
1978
|
+
const allMemories = await ctx.memoriesCollection.query(import_watermelondb2.Q.where("is_deleted", false)).fetch();
|
|
1979
|
+
const memoriesWithEmbeddings = allMemories.filter(
|
|
1980
|
+
(m) => m.embedding && m.embedding.length > 0
|
|
1981
|
+
);
|
|
1982
|
+
if (memoriesWithEmbeddings.length === 0) {
|
|
1983
|
+
return [];
|
|
1984
|
+
}
|
|
1985
|
+
const results = memoriesWithEmbeddings.map((memory) => {
|
|
1986
|
+
const similarity = cosineSimilarity(queryEmbedding, memory.embedding);
|
|
1987
|
+
return {
|
|
1988
|
+
...memoryToStored(memory),
|
|
1989
|
+
similarity
|
|
1990
|
+
};
|
|
1991
|
+
}).filter((result) => result.similarity >= minSimilarity).sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
1992
|
+
return results;
|
|
1993
|
+
}
|
|
1994
|
+
async function updateMemoryEmbeddingOp(ctx, id, embedding, embeddingModel) {
|
|
1995
|
+
try {
|
|
1996
|
+
const memory = await ctx.memoriesCollection.find(id);
|
|
1997
|
+
await ctx.database.write(async () => {
|
|
1998
|
+
await memory.update((mem) => {
|
|
1999
|
+
mem._setRaw("embedding", JSON.stringify(embedding));
|
|
2000
|
+
mem._setRaw("embedding_model", embeddingModel);
|
|
2001
|
+
});
|
|
2002
|
+
});
|
|
2003
|
+
} catch {
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// src/lib/memory/service.ts
|
|
2008
|
+
var FACT_EXTRACTION_PROMPT = `You are a memory extraction system. Extract durable user memories from chat messages.
|
|
2009
|
+
|
|
2010
|
+
CRITICAL: You MUST respond with ONLY valid JSON. No explanations, no markdown, no code blocks, just pure JSON.
|
|
2011
|
+
|
|
2012
|
+
Only store clear, factual statements that might be relevant for future context or reference. Extract facts that will be useful in future conversations, such as identity, stable preferences, ongoing projects, skills, locations, favorites, and constraints.
|
|
2013
|
+
|
|
2014
|
+
Do not extract sensitive attributes, temporary things, or single-use instructions.
|
|
2015
|
+
|
|
2016
|
+
You must also extract stable personal preferences, including food likes/dislikes, hobbies, favorite items, favorite genres, or other enduring tastes.
|
|
2017
|
+
|
|
2018
|
+
If there are no memories to extract, return: {"items": []}
|
|
2019
|
+
|
|
2020
|
+
Response format (JSON only, no other text):
|
|
2021
|
+
|
|
2022
|
+
{
|
|
2023
|
+
"items": [
|
|
2024
|
+
{
|
|
2025
|
+
"type": "identity",
|
|
2026
|
+
"namespace": "identity",
|
|
2027
|
+
"key": "name",
|
|
2028
|
+
"value": "Charlie",
|
|
2029
|
+
"rawEvidence": "I'm Charlie",
|
|
2030
|
+
"confidence": 0.98,
|
|
2031
|
+
"pii": true
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
"type": "identity",
|
|
2035
|
+
"namespace": "work",
|
|
2036
|
+
"key": "company",
|
|
2037
|
+
"value": "ZetaChain",
|
|
2038
|
+
"rawEvidence": "called ZetaChain",
|
|
2039
|
+
"confidence": 0.99,
|
|
2040
|
+
"pii": false
|
|
2041
|
+
},
|
|
2042
|
+
{
|
|
2043
|
+
"type": "identity",
|
|
2044
|
+
"namespace": "location",
|
|
2045
|
+
"key": "city",
|
|
2046
|
+
"value": "San Francisco",
|
|
2047
|
+
"rawEvidence": "I live in San Francisco",
|
|
2048
|
+
"confidence": 0.99,
|
|
2049
|
+
"pii": false
|
|
2050
|
+
},
|
|
2051
|
+
{
|
|
2052
|
+
"type": "preference",
|
|
2053
|
+
"namespace": "location",
|
|
2054
|
+
"key": "country",
|
|
2055
|
+
"value": "Japan",
|
|
2056
|
+
"rawEvidence": "I like to travel to the Japan",
|
|
2057
|
+
"confidence": 0.94,
|
|
2058
|
+
"pii": false
|
|
2059
|
+
},
|
|
2060
|
+
{
|
|
2061
|
+
"type": "preference",
|
|
2062
|
+
"namespace": "answer_style",
|
|
2063
|
+
"key": "verbosity",
|
|
2064
|
+
"value": "concise_direct",
|
|
2065
|
+
"rawEvidence": "I prefer concise, direct answers",
|
|
2066
|
+
"confidence": 0.96,
|
|
2067
|
+
"pii": false
|
|
2068
|
+
},
|
|
2069
|
+
{
|
|
2070
|
+
"type": "identity",
|
|
2071
|
+
"namespace": "timezone",
|
|
2072
|
+
"key": "tz",
|
|
2073
|
+
"value": "America/Los_Angeles",
|
|
2074
|
+
"rawEvidence": "I'm in PST",
|
|
2075
|
+
"confidence": 0.9,
|
|
2076
|
+
"pii": false
|
|
2077
|
+
},
|
|
2078
|
+
{
|
|
2079
|
+
"type": "preference",
|
|
2080
|
+
"namespace": "food",
|
|
2081
|
+
"key": "likes_ice_cream",
|
|
2082
|
+
"value": "ice cream",
|
|
2083
|
+
"rawEvidence": "I like ice cream",
|
|
2084
|
+
"confidence": 0.95,
|
|
2085
|
+
"pii": false
|
|
2086
|
+
}
|
|
2087
|
+
]
|
|
2088
|
+
}`;
|
|
2089
|
+
var preprocessMemories = (items, minConfidence = 0.6) => {
|
|
2090
|
+
if (!items || !Array.isArray(items)) {
|
|
2091
|
+
return [];
|
|
2092
|
+
}
|
|
2093
|
+
const validItems = items.filter((item) => {
|
|
2094
|
+
if (item.namespace == null || item.key == null || item.value == null) {
|
|
2095
|
+
console.warn(
|
|
2096
|
+
"Dropping memory item with null/undefined namespace, key, or value:",
|
|
2097
|
+
item
|
|
2098
|
+
);
|
|
2099
|
+
return false;
|
|
2100
|
+
}
|
|
2101
|
+
const namespace = String(item.namespace).trim();
|
|
2102
|
+
const key = String(item.key).trim();
|
|
2103
|
+
const value = String(item.value).trim();
|
|
2104
|
+
if (namespace === "" || key === "" || value === "") {
|
|
2105
|
+
console.warn(
|
|
2106
|
+
"Dropping memory item with empty namespace, key, or value after trimming:",
|
|
2107
|
+
item
|
|
2108
|
+
);
|
|
2109
|
+
return false;
|
|
2110
|
+
}
|
|
2111
|
+
if (typeof item.confidence !== "number" || item.confidence < minConfidence) {
|
|
2112
|
+
console.warn(
|
|
2113
|
+
`Dropping memory item with confidence ${item.confidence} below threshold ${minConfidence}:`,
|
|
2114
|
+
item
|
|
2115
|
+
);
|
|
2116
|
+
return false;
|
|
2117
|
+
}
|
|
2118
|
+
return true;
|
|
2119
|
+
});
|
|
2120
|
+
const deduplicatedMap = /* @__PURE__ */ new Map();
|
|
2121
|
+
for (const item of validItems) {
|
|
2122
|
+
const uniqueKey = `${item.namespace}:${item.key}:${item.value}`;
|
|
2123
|
+
const existing = deduplicatedMap.get(uniqueKey);
|
|
2124
|
+
if (!existing || item.confidence > existing.confidence) {
|
|
2125
|
+
deduplicatedMap.set(uniqueKey, item);
|
|
2126
|
+
} else {
|
|
2127
|
+
console.debug(
|
|
2128
|
+
`Deduplicating memory item: keeping entry with higher confidence (${existing.confidence} > ${item.confidence})`,
|
|
2129
|
+
{ namespace: item.namespace, key: item.key, value: item.value }
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return Array.from(deduplicatedMap.values());
|
|
2134
|
+
};
|
|
2135
|
+
|
|
2136
|
+
// src/lib/memory/constants.ts
|
|
2137
|
+
var DEFAULT_API_EMBEDDING_MODEL = "openai/text-embedding-3-small";
|
|
2138
|
+
var DEFAULT_COMPLETION_MODEL = "openai/gpt-4o";
|
|
2139
|
+
|
|
2140
|
+
// src/expo/useMemoryStorage.ts
|
|
2141
|
+
var import_client5 = require("@reverbia/sdk");
|
|
2142
|
+
async function generateEmbeddingForTextApi(text, options) {
|
|
2143
|
+
const token = options.getToken ? await options.getToken() : null;
|
|
2144
|
+
if (!token) {
|
|
2145
|
+
throw new Error("No auth token available for embedding generation");
|
|
2146
|
+
}
|
|
2147
|
+
const response = await (0, import_client5.postApiV1Embeddings)({
|
|
2148
|
+
baseUrl: options.baseUrl,
|
|
2149
|
+
body: {
|
|
2150
|
+
input: text,
|
|
2151
|
+
model: options.model
|
|
2152
|
+
},
|
|
2153
|
+
headers: {
|
|
2154
|
+
Authorization: `Bearer ${token}`
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
if (!response.data || typeof response.data === "string") {
|
|
2158
|
+
throw new Error("Failed to generate embedding");
|
|
2159
|
+
}
|
|
2160
|
+
const embedding = response.data.data?.[0]?.embedding;
|
|
2161
|
+
if (!embedding) {
|
|
2162
|
+
throw new Error("No embedding in response");
|
|
2163
|
+
}
|
|
2164
|
+
return embedding;
|
|
2165
|
+
}
|
|
2166
|
+
async function generateEmbeddingForMemoryApi(memory, options) {
|
|
2167
|
+
const text = `${memory.type}: ${memory.namespace}/${memory.key} = ${memory.value}. Evidence: ${memory.rawEvidence}`;
|
|
2168
|
+
return generateEmbeddingForTextApi(text, options);
|
|
2169
|
+
}
|
|
2170
|
+
function useMemoryStorage(options) {
|
|
2171
|
+
const {
|
|
2172
|
+
database,
|
|
2173
|
+
completionsModel = DEFAULT_COMPLETION_MODEL,
|
|
2174
|
+
embeddingModel: userEmbeddingModel,
|
|
2175
|
+
embeddingProvider = "api",
|
|
2176
|
+
// Default to API for Expo
|
|
2177
|
+
generateEmbeddings = true,
|
|
2178
|
+
onFactsExtracted,
|
|
2179
|
+
getToken,
|
|
2180
|
+
baseUrl = BASE_URL
|
|
2181
|
+
} = options;
|
|
2182
|
+
const embeddingModel = userEmbeddingModel === void 0 ? DEFAULT_API_EMBEDDING_MODEL : userEmbeddingModel;
|
|
2183
|
+
const [memories, setMemories] = (0, import_react5.useState)([]);
|
|
2184
|
+
const extractionInProgressRef = (0, import_react5.useRef)(false);
|
|
2185
|
+
const memoriesCollection = (0, import_react5.useMemo)(
|
|
2186
|
+
() => database.get("memories"),
|
|
2187
|
+
[database]
|
|
2188
|
+
);
|
|
2189
|
+
const storageCtx = (0, import_react5.useMemo)(
|
|
2190
|
+
() => ({
|
|
2191
|
+
database,
|
|
2192
|
+
memoriesCollection
|
|
2193
|
+
}),
|
|
2194
|
+
[database, memoriesCollection]
|
|
2195
|
+
);
|
|
2196
|
+
const embeddingOptions = (0, import_react5.useMemo)(
|
|
2197
|
+
() => ({
|
|
2198
|
+
model: embeddingModel ?? DEFAULT_API_EMBEDDING_MODEL,
|
|
2199
|
+
getToken: getToken || void 0,
|
|
2200
|
+
baseUrl
|
|
2201
|
+
}),
|
|
2202
|
+
[embeddingModel, getToken, baseUrl]
|
|
2203
|
+
);
|
|
2204
|
+
const refreshMemories = (0, import_react5.useCallback)(async () => {
|
|
2205
|
+
const storedMemories = await getAllMemoriesOp(storageCtx);
|
|
2206
|
+
setMemories(storedMemories);
|
|
2207
|
+
}, [storageCtx]);
|
|
2208
|
+
const extractMemoriesFromMessage = (0, import_react5.useCallback)(
|
|
2209
|
+
async (opts) => {
|
|
2210
|
+
const { messages, model } = opts;
|
|
2211
|
+
if (!getToken || extractionInProgressRef.current) {
|
|
2212
|
+
return null;
|
|
2213
|
+
}
|
|
2214
|
+
extractionInProgressRef.current = true;
|
|
2215
|
+
try {
|
|
2216
|
+
const token = await getToken();
|
|
2217
|
+
if (!token) {
|
|
2218
|
+
console.error("No access token available for memory extraction");
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
const completion = await (0, import_client4.postApiV1ChatCompletions)({
|
|
2222
|
+
baseUrl,
|
|
2223
|
+
body: {
|
|
2224
|
+
messages: [
|
|
2225
|
+
{
|
|
2226
|
+
role: "system",
|
|
2227
|
+
content: [{ type: "text", text: FACT_EXTRACTION_PROMPT }]
|
|
2228
|
+
},
|
|
2229
|
+
...messages.map((m) => ({
|
|
2230
|
+
role: m.role,
|
|
2231
|
+
content: [{ type: "text", text: m.content }]
|
|
2232
|
+
}))
|
|
2233
|
+
],
|
|
2234
|
+
model: model || completionsModel
|
|
2235
|
+
},
|
|
2236
|
+
headers: {
|
|
2237
|
+
Authorization: `Bearer ${token}`
|
|
2238
|
+
}
|
|
2239
|
+
});
|
|
2240
|
+
if (!completion.data) {
|
|
2241
|
+
console.error(
|
|
2242
|
+
"Memory extraction failed:",
|
|
2243
|
+
completion.error?.error ?? "API did not return a response"
|
|
2244
|
+
);
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2247
|
+
if (typeof completion.data === "string") {
|
|
2248
|
+
console.error(
|
|
2249
|
+
"Memory extraction failed: API returned a string response"
|
|
2250
|
+
);
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
const messageContent = completion.data.choices?.[0]?.message?.content;
|
|
2254
|
+
let content = "";
|
|
2255
|
+
if (Array.isArray(messageContent)) {
|
|
2256
|
+
content = messageContent.map((p) => p.text || "").join("").trim();
|
|
2257
|
+
} else if (typeof messageContent === "string") {
|
|
2258
|
+
content = messageContent.trim();
|
|
2259
|
+
}
|
|
2260
|
+
if (!content) {
|
|
2261
|
+
console.error("No content in memory extraction response");
|
|
2262
|
+
return null;
|
|
2263
|
+
}
|
|
2264
|
+
let jsonContent = content;
|
|
2265
|
+
jsonContent = jsonContent.replace(/^data:\s*/gm, "").trim();
|
|
2266
|
+
if (jsonContent.startsWith("{")) {
|
|
2267
|
+
let braceCount = 0;
|
|
2268
|
+
let jsonStart = -1;
|
|
2269
|
+
let jsonEnd = -1;
|
|
2270
|
+
for (let i = 0; i < jsonContent.length; i++) {
|
|
2271
|
+
if (jsonContent[i] === "{") {
|
|
2272
|
+
if (jsonStart === -1) jsonStart = i;
|
|
2273
|
+
braceCount++;
|
|
2274
|
+
} else if (jsonContent[i] === "}") {
|
|
2275
|
+
braceCount--;
|
|
2276
|
+
if (braceCount === 0 && jsonStart !== -1) {
|
|
2277
|
+
jsonEnd = i + 1;
|
|
2278
|
+
break;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
if (jsonStart !== -1 && jsonEnd !== -1) {
|
|
2283
|
+
jsonContent = jsonContent.substring(jsonStart, jsonEnd);
|
|
2284
|
+
}
|
|
2285
|
+
} else {
|
|
2286
|
+
const jsonMatch = jsonContent.match(
|
|
2287
|
+
/```(?:json)?\s*(\{[\s\S]*?\})\s*```/
|
|
2288
|
+
);
|
|
2289
|
+
if (jsonMatch && jsonMatch[1]) {
|
|
2290
|
+
jsonContent = jsonMatch[1].trim();
|
|
2291
|
+
} else {
|
|
2292
|
+
const jsonObjectMatch = jsonContent.match(/\{[\s\S]*\}/);
|
|
2293
|
+
if (jsonObjectMatch && jsonObjectMatch[0]) {
|
|
2294
|
+
jsonContent = jsonObjectMatch[0];
|
|
2295
|
+
} else {
|
|
2296
|
+
console.warn("Memory extraction returned non-JSON response.");
|
|
2297
|
+
return { items: [] };
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
const trimmedJson = jsonContent.trim();
|
|
2302
|
+
if (!trimmedJson.startsWith("{") || !trimmedJson.includes("items")) {
|
|
2303
|
+
console.warn("Memory extraction response doesn't appear to be valid JSON.");
|
|
2304
|
+
return { items: [] };
|
|
2305
|
+
}
|
|
2306
|
+
let result;
|
|
2307
|
+
try {
|
|
2308
|
+
result = JSON.parse(jsonContent);
|
|
2309
|
+
if (!result || typeof result !== "object") {
|
|
2310
|
+
throw new Error("Invalid JSON structure: not an object");
|
|
2311
|
+
}
|
|
2312
|
+
if (!Array.isArray(result.items)) {
|
|
2313
|
+
console.warn("Memory extraction result missing 'items' array.");
|
|
2314
|
+
return { items: [] };
|
|
2315
|
+
}
|
|
2316
|
+
} catch (parseError) {
|
|
2317
|
+
console.error(
|
|
2318
|
+
"Failed to parse memory extraction JSON:",
|
|
2319
|
+
parseError instanceof Error ? parseError.message : "Unknown error"
|
|
2320
|
+
);
|
|
2321
|
+
return { items: [] };
|
|
2322
|
+
}
|
|
2323
|
+
if (result.items && Array.isArray(result.items)) {
|
|
2324
|
+
result.items = preprocessMemories(result.items);
|
|
2325
|
+
}
|
|
2326
|
+
if (result.items && result.items.length > 0) {
|
|
2327
|
+
try {
|
|
2328
|
+
const createOptions = result.items.map(
|
|
2329
|
+
(item) => ({
|
|
2330
|
+
type: item.type,
|
|
2331
|
+
namespace: item.namespace,
|
|
2332
|
+
key: item.key,
|
|
2333
|
+
value: item.value,
|
|
2334
|
+
rawEvidence: item.rawEvidence,
|
|
2335
|
+
confidence: item.confidence,
|
|
2336
|
+
pii: item.pii
|
|
2337
|
+
})
|
|
2338
|
+
);
|
|
2339
|
+
const savedMemories = await saveMemoriesOp(storageCtx, createOptions);
|
|
2340
|
+
console.log(`Saved ${savedMemories.length} memories to WatermelonDB`);
|
|
2341
|
+
if (generateEmbeddings && embeddingModel) {
|
|
2342
|
+
try {
|
|
2343
|
+
for (const saved of savedMemories) {
|
|
2344
|
+
const memoryItem = {
|
|
2345
|
+
type: saved.type,
|
|
2346
|
+
namespace: saved.namespace,
|
|
2347
|
+
key: saved.key,
|
|
2348
|
+
value: saved.value,
|
|
2349
|
+
rawEvidence: saved.rawEvidence,
|
|
2350
|
+
confidence: saved.confidence,
|
|
2351
|
+
pii: saved.pii
|
|
2352
|
+
};
|
|
2353
|
+
const embedding = await generateEmbeddingForMemoryApi(
|
|
2354
|
+
memoryItem,
|
|
2355
|
+
embeddingOptions
|
|
2356
|
+
);
|
|
2357
|
+
await updateMemoryEmbeddingOp(
|
|
2358
|
+
storageCtx,
|
|
2359
|
+
saved.uniqueId,
|
|
2360
|
+
embedding,
|
|
2361
|
+
embeddingOptions.model
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
console.log(`Generated embeddings for ${savedMemories.length} memories`);
|
|
2365
|
+
} catch (error) {
|
|
2366
|
+
console.error("Failed to generate embeddings:", error);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
await refreshMemories();
|
|
2370
|
+
} catch (error) {
|
|
2371
|
+
console.error("Failed to save memories to WatermelonDB:", error);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
if (onFactsExtracted) {
|
|
2375
|
+
onFactsExtracted(result);
|
|
2376
|
+
}
|
|
2377
|
+
return result;
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
console.error("Failed to extract facts:", error);
|
|
2380
|
+
return null;
|
|
2381
|
+
} finally {
|
|
2382
|
+
extractionInProgressRef.current = false;
|
|
2383
|
+
}
|
|
2384
|
+
},
|
|
2385
|
+
[
|
|
2386
|
+
completionsModel,
|
|
2387
|
+
embeddingModel,
|
|
2388
|
+
embeddingOptions,
|
|
2389
|
+
generateEmbeddings,
|
|
2390
|
+
getToken,
|
|
2391
|
+
onFactsExtracted,
|
|
2392
|
+
baseUrl,
|
|
2393
|
+
storageCtx,
|
|
2394
|
+
refreshMemories
|
|
2395
|
+
]
|
|
2396
|
+
);
|
|
2397
|
+
const searchMemories = (0, import_react5.useCallback)(
|
|
2398
|
+
async (query, limit = 10, minSimilarity = 0.6) => {
|
|
2399
|
+
if (!embeddingModel) {
|
|
2400
|
+
console.warn("Cannot search memories: embeddingModel not provided");
|
|
2401
|
+
return [];
|
|
2402
|
+
}
|
|
2403
|
+
try {
|
|
2404
|
+
const queryEmbedding = await generateEmbeddingForTextApi(
|
|
2405
|
+
query,
|
|
2406
|
+
embeddingOptions
|
|
2407
|
+
);
|
|
2408
|
+
const results = await searchSimilarMemoriesOp(
|
|
2409
|
+
storageCtx,
|
|
2410
|
+
queryEmbedding,
|
|
2411
|
+
limit,
|
|
2412
|
+
minSimilarity
|
|
2413
|
+
);
|
|
2414
|
+
if (results.length === 0) {
|
|
2415
|
+
console.warn(
|
|
2416
|
+
`[Memory Search] No memories found above similarity threshold ${minSimilarity}.`
|
|
2417
|
+
);
|
|
2418
|
+
} else {
|
|
2419
|
+
console.log(
|
|
2420
|
+
`[Memory Search] Found ${results.length} memories. Similarity scores: ${results.map((r) => r.similarity.toFixed(3)).join(", ")}`
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
return results;
|
|
2424
|
+
} catch {
|
|
2425
|
+
return [];
|
|
2426
|
+
}
|
|
2427
|
+
},
|
|
2428
|
+
[embeddingModel, embeddingOptions, storageCtx]
|
|
2429
|
+
);
|
|
2430
|
+
const fetchAllMemories = (0, import_react5.useCallback)(async () => {
|
|
2431
|
+
try {
|
|
2432
|
+
return await getAllMemoriesOp(storageCtx);
|
|
2433
|
+
} catch (error) {
|
|
2434
|
+
throw new Error(
|
|
2435
|
+
"Failed to fetch all memories: " + (error instanceof Error ? error.message : String(error))
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
}, [storageCtx]);
|
|
2439
|
+
const fetchMemoriesByNamespace = (0, import_react5.useCallback)(
|
|
2440
|
+
async (namespace) => {
|
|
2441
|
+
if (!namespace) {
|
|
2442
|
+
throw new Error("Missing required field: namespace");
|
|
2443
|
+
}
|
|
2444
|
+
try {
|
|
2445
|
+
return await getMemoriesByNamespaceOp(storageCtx, namespace);
|
|
2446
|
+
} catch (error) {
|
|
2447
|
+
throw new Error(
|
|
2448
|
+
`Failed to fetch memories for namespace "${namespace}": ` + (error instanceof Error ? error.message : String(error))
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
},
|
|
2452
|
+
[storageCtx]
|
|
2453
|
+
);
|
|
2454
|
+
const fetchMemoriesByKey = (0, import_react5.useCallback)(
|
|
2455
|
+
async (namespace, key) => {
|
|
2456
|
+
if (!namespace || !key) {
|
|
2457
|
+
throw new Error("Missing required fields: namespace, key");
|
|
2458
|
+
}
|
|
2459
|
+
try {
|
|
2460
|
+
return await getMemoriesByKeyOp(storageCtx, namespace, key);
|
|
2461
|
+
} catch (error) {
|
|
2462
|
+
throw new Error(
|
|
2463
|
+
`Failed to fetch memories for "${namespace}:${key}": ` + (error instanceof Error ? error.message : String(error))
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
},
|
|
2467
|
+
[storageCtx]
|
|
2468
|
+
);
|
|
2469
|
+
const getMemoryById = (0, import_react5.useCallback)(
|
|
2470
|
+
async (id) => {
|
|
2471
|
+
try {
|
|
2472
|
+
return await getMemoryByIdOp(storageCtx, id);
|
|
2473
|
+
} catch (error) {
|
|
2474
|
+
throw new Error(
|
|
2475
|
+
`Failed to get memory ${id}: ` + (error instanceof Error ? error.message : String(error))
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
},
|
|
2479
|
+
[storageCtx]
|
|
2480
|
+
);
|
|
2481
|
+
const saveMemory = (0, import_react5.useCallback)(
|
|
2482
|
+
async (memory) => {
|
|
2483
|
+
try {
|
|
2484
|
+
const saved = await saveMemoryOp(storageCtx, memory);
|
|
2485
|
+
if (generateEmbeddings && embeddingModel && !memory.embedding) {
|
|
2486
|
+
try {
|
|
2487
|
+
const memoryItem = {
|
|
2488
|
+
type: memory.type,
|
|
2489
|
+
namespace: memory.namespace,
|
|
2490
|
+
key: memory.key,
|
|
2491
|
+
value: memory.value,
|
|
2492
|
+
rawEvidence: memory.rawEvidence,
|
|
2493
|
+
confidence: memory.confidence,
|
|
2494
|
+
pii: memory.pii
|
|
2495
|
+
};
|
|
2496
|
+
const embedding = await generateEmbeddingForMemoryApi(
|
|
2497
|
+
memoryItem,
|
|
2498
|
+
embeddingOptions
|
|
2499
|
+
);
|
|
2500
|
+
await updateMemoryEmbeddingOp(
|
|
2501
|
+
storageCtx,
|
|
2502
|
+
saved.uniqueId,
|
|
2503
|
+
embedding,
|
|
2504
|
+
embeddingOptions.model
|
|
2505
|
+
);
|
|
2506
|
+
} catch (error) {
|
|
2507
|
+
console.error("Failed to generate embedding:", error);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
setMemories((prev) => {
|
|
2511
|
+
const existing = prev.find((m) => m.uniqueId === saved.uniqueId);
|
|
2512
|
+
if (existing) {
|
|
2513
|
+
return prev.map((m) => m.uniqueId === saved.uniqueId ? saved : m);
|
|
2514
|
+
}
|
|
2515
|
+
return [saved, ...prev];
|
|
2516
|
+
});
|
|
2517
|
+
return saved;
|
|
2518
|
+
} catch (error) {
|
|
2519
|
+
throw new Error(
|
|
2520
|
+
"Failed to save memory: " + (error instanceof Error ? error.message : String(error))
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
},
|
|
2524
|
+
[storageCtx, generateEmbeddings, embeddingModel, embeddingOptions]
|
|
2525
|
+
);
|
|
2526
|
+
const saveMemories = (0, import_react5.useCallback)(
|
|
2527
|
+
async (memoriesToSave) => {
|
|
2528
|
+
try {
|
|
2529
|
+
const saved = await saveMemoriesOp(storageCtx, memoriesToSave);
|
|
2530
|
+
if (generateEmbeddings && embeddingModel) {
|
|
2531
|
+
for (let i = 0; i < saved.length; i++) {
|
|
2532
|
+
const memory = memoriesToSave[i];
|
|
2533
|
+
if (!memory.embedding) {
|
|
2534
|
+
try {
|
|
2535
|
+
const memoryItem = {
|
|
2536
|
+
type: memory.type,
|
|
2537
|
+
namespace: memory.namespace,
|
|
2538
|
+
key: memory.key,
|
|
2539
|
+
value: memory.value,
|
|
2540
|
+
rawEvidence: memory.rawEvidence,
|
|
2541
|
+
confidence: memory.confidence,
|
|
2542
|
+
pii: memory.pii
|
|
2543
|
+
};
|
|
2544
|
+
const embedding = await generateEmbeddingForMemoryApi(
|
|
2545
|
+
memoryItem,
|
|
2546
|
+
embeddingOptions
|
|
2547
|
+
);
|
|
2548
|
+
await updateMemoryEmbeddingOp(
|
|
2549
|
+
storageCtx,
|
|
2550
|
+
saved[i].uniqueId,
|
|
2551
|
+
embedding,
|
|
2552
|
+
embeddingOptions.model
|
|
2553
|
+
);
|
|
2554
|
+
} catch (error) {
|
|
2555
|
+
console.error("Failed to generate embedding:", error);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
await refreshMemories();
|
|
2561
|
+
return saved;
|
|
2562
|
+
} catch (error) {
|
|
2563
|
+
throw new Error(
|
|
2564
|
+
"Failed to save memories: " + (error instanceof Error ? error.message : String(error))
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
[storageCtx, generateEmbeddings, embeddingModel, embeddingOptions, refreshMemories]
|
|
2569
|
+
);
|
|
2570
|
+
const updateMemory = (0, import_react5.useCallback)(
|
|
2571
|
+
async (id, updates) => {
|
|
2572
|
+
const result = await updateMemoryOp(storageCtx, id, updates);
|
|
2573
|
+
if (!result.ok) {
|
|
2574
|
+
if (result.reason === "not_found") {
|
|
2575
|
+
return null;
|
|
2576
|
+
}
|
|
2577
|
+
if (result.reason === "conflict") {
|
|
2578
|
+
throw new Error(
|
|
2579
|
+
`Cannot update memory: a memory with key "${result.conflictingKey}" already exists`
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
throw new Error(
|
|
2583
|
+
`Failed to update memory ${id}: ${result.error.message}`
|
|
2584
|
+
);
|
|
2585
|
+
}
|
|
2586
|
+
const updated = result.memory;
|
|
2587
|
+
const contentChanged = updates.value !== void 0 || updates.rawEvidence !== void 0 || updates.type !== void 0 || updates.namespace !== void 0 || updates.key !== void 0;
|
|
2588
|
+
if (contentChanged && generateEmbeddings && embeddingModel && !updates.embedding) {
|
|
2589
|
+
try {
|
|
2590
|
+
const memoryItem = {
|
|
2591
|
+
type: updated.type,
|
|
2592
|
+
namespace: updated.namespace,
|
|
2593
|
+
key: updated.key,
|
|
2594
|
+
value: updated.value,
|
|
2595
|
+
rawEvidence: updated.rawEvidence,
|
|
2596
|
+
confidence: updated.confidence,
|
|
2597
|
+
pii: updated.pii
|
|
2598
|
+
};
|
|
2599
|
+
const embedding = await generateEmbeddingForMemoryApi(
|
|
2600
|
+
memoryItem,
|
|
2601
|
+
embeddingOptions
|
|
2602
|
+
);
|
|
2603
|
+
await updateMemoryEmbeddingOp(storageCtx, id, embedding, embeddingOptions.model);
|
|
2604
|
+
} catch (error) {
|
|
2605
|
+
console.error("Failed to regenerate embedding:", error);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
setMemories((prev) => prev.map((m) => m.uniqueId === id ? updated : m));
|
|
2609
|
+
return updated;
|
|
2610
|
+
},
|
|
2611
|
+
[storageCtx, generateEmbeddings, embeddingModel, embeddingOptions]
|
|
2612
|
+
);
|
|
2613
|
+
const removeMemory = (0, import_react5.useCallback)(
|
|
2614
|
+
async (namespace, key, value) => {
|
|
2615
|
+
if (!namespace || !key || !value) {
|
|
2616
|
+
throw new Error("Missing required fields: namespace, key, value");
|
|
2617
|
+
}
|
|
2618
|
+
try {
|
|
2619
|
+
await deleteMemoryOp(storageCtx, namespace, key, value);
|
|
2620
|
+
setMemories(
|
|
2621
|
+
(prev) => prev.filter(
|
|
2622
|
+
(m) => !(m.namespace === namespace && m.key === key && m.value === value)
|
|
2623
|
+
)
|
|
2624
|
+
);
|
|
2625
|
+
} catch (error) {
|
|
2626
|
+
throw new Error(
|
|
2627
|
+
`Failed to delete memory "${namespace}:${key}:${value}": ` + (error instanceof Error ? error.message : String(error))
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
},
|
|
2631
|
+
[storageCtx]
|
|
2632
|
+
);
|
|
2633
|
+
const removeMemoryById = (0, import_react5.useCallback)(
|
|
2634
|
+
async (id) => {
|
|
2635
|
+
try {
|
|
2636
|
+
await deleteMemoryByIdOp(storageCtx, id);
|
|
2637
|
+
setMemories((prev) => prev.filter((m) => m.uniqueId !== id));
|
|
2638
|
+
} catch (error) {
|
|
2639
|
+
throw new Error(
|
|
2640
|
+
`Failed to delete memory with id ${id}: ` + (error instanceof Error ? error.message : String(error))
|
|
2641
|
+
);
|
|
2642
|
+
}
|
|
2643
|
+
},
|
|
2644
|
+
[storageCtx]
|
|
2645
|
+
);
|
|
2646
|
+
const removeMemories = (0, import_react5.useCallback)(
|
|
2647
|
+
async (namespace, key) => {
|
|
2648
|
+
if (!namespace || !key) {
|
|
2649
|
+
throw new Error("Missing required fields: namespace, key");
|
|
2650
|
+
}
|
|
2651
|
+
try {
|
|
2652
|
+
await deleteMemoriesByKeyOp(storageCtx, namespace, key);
|
|
2653
|
+
setMemories(
|
|
2654
|
+
(prev) => prev.filter((m) => !(m.namespace === namespace && m.key === key))
|
|
2655
|
+
);
|
|
2656
|
+
} catch (error) {
|
|
2657
|
+
throw new Error(
|
|
2658
|
+
`Failed to delete memories for "${namespace}:${key}": ` + (error instanceof Error ? error.message : String(error))
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
},
|
|
2662
|
+
[storageCtx]
|
|
2663
|
+
);
|
|
2664
|
+
const clearMemories = (0, import_react5.useCallback)(async () => {
|
|
2665
|
+
try {
|
|
2666
|
+
await clearAllMemoriesOp(storageCtx);
|
|
2667
|
+
setMemories([]);
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
throw new Error(
|
|
2670
|
+
"Failed to clear all memories: " + (error instanceof Error ? error.message : String(error))
|
|
2671
|
+
);
|
|
2672
|
+
}
|
|
2673
|
+
}, [storageCtx]);
|
|
2674
|
+
return {
|
|
2675
|
+
memories,
|
|
2676
|
+
refreshMemories,
|
|
2677
|
+
extractMemoriesFromMessage,
|
|
2678
|
+
searchMemories,
|
|
2679
|
+
fetchAllMemories,
|
|
2680
|
+
fetchMemoriesByNamespace,
|
|
2681
|
+
fetchMemoriesByKey,
|
|
2682
|
+
getMemoryById,
|
|
2683
|
+
saveMemory,
|
|
2684
|
+
saveMemories,
|
|
2685
|
+
updateMemory,
|
|
2686
|
+
removeMemory,
|
|
2687
|
+
removeMemoryById,
|
|
2688
|
+
removeMemories,
|
|
2689
|
+
clearMemories
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// src/lib/chatStorage/schema.ts
|
|
2694
|
+
var import_watermelondb3 = require("@nozbe/watermelondb");
|
|
2695
|
+
var chatStorageSchema = (0, import_watermelondb3.appSchema)({
|
|
2696
|
+
version: 1,
|
|
2697
|
+
tables: [
|
|
2698
|
+
(0, import_watermelondb3.tableSchema)({
|
|
2699
|
+
name: "history",
|
|
2700
|
+
columns: [
|
|
2701
|
+
{ name: "message_id", type: "number" },
|
|
2702
|
+
// Sequential ID within conversation
|
|
2703
|
+
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
2704
|
+
{ name: "role", type: "string", isIndexed: true },
|
|
2705
|
+
// 'user' | 'assistant' | 'system'
|
|
2706
|
+
{ name: "content", type: "string" },
|
|
2707
|
+
{ name: "model", type: "string", isOptional: true },
|
|
2708
|
+
{ name: "files", type: "string", isOptional: true },
|
|
2709
|
+
// JSON stringified FileMetadata[]
|
|
2710
|
+
{ name: "created_at", type: "number", isIndexed: true },
|
|
2711
|
+
{ name: "updated_at", type: "number" },
|
|
2712
|
+
{ name: "vector", type: "string", isOptional: true },
|
|
2713
|
+
// JSON stringified number[]
|
|
2714
|
+
{ name: "embedding_model", type: "string", isOptional: true },
|
|
2715
|
+
{ name: "usage", type: "string", isOptional: true },
|
|
2716
|
+
// JSON stringified ChatCompletionUsage
|
|
2717
|
+
{ name: "sources", type: "string", isOptional: true },
|
|
2718
|
+
// JSON stringified SearchSource[]
|
|
2719
|
+
{ name: "response_duration", type: "number", isOptional: true }
|
|
2720
|
+
]
|
|
2721
|
+
}),
|
|
2722
|
+
(0, import_watermelondb3.tableSchema)({
|
|
2723
|
+
name: "conversations",
|
|
2724
|
+
columns: [
|
|
2725
|
+
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
2726
|
+
{ name: "title", type: "string" },
|
|
2727
|
+
{ name: "created_at", type: "number" },
|
|
2728
|
+
{ name: "updated_at", type: "number" },
|
|
2729
|
+
{ name: "is_deleted", type: "boolean", isIndexed: true }
|
|
2730
|
+
]
|
|
2731
|
+
})
|
|
2732
|
+
]
|
|
2733
|
+
});
|
|
2734
|
+
|
|
2735
|
+
// src/lib/chatStorage/models.ts
|
|
2736
|
+
var import_watermelondb4 = require("@nozbe/watermelondb");
|
|
2737
|
+
var Message = class extends import_watermelondb4.Model {
|
|
2738
|
+
/** Sequential message ID within conversation */
|
|
2739
|
+
get messageId() {
|
|
2740
|
+
return this._getRaw("message_id");
|
|
2741
|
+
}
|
|
2742
|
+
/** Links message to its conversation */
|
|
2743
|
+
get conversationId() {
|
|
2744
|
+
return this._getRaw("conversation_id");
|
|
2745
|
+
}
|
|
2746
|
+
/** Who sent the message: 'user' | 'assistant' | 'system' */
|
|
2747
|
+
get role() {
|
|
2748
|
+
return this._getRaw("role");
|
|
2749
|
+
}
|
|
2750
|
+
/** The message text content */
|
|
2751
|
+
get content() {
|
|
2752
|
+
return this._getRaw("content");
|
|
2753
|
+
}
|
|
2754
|
+
/** LLM model used (e.g., GPT-4, Claude) */
|
|
2755
|
+
get model() {
|
|
2756
|
+
const value = this._getRaw("model");
|
|
2757
|
+
return value ? value : void 0;
|
|
2758
|
+
}
|
|
2759
|
+
/** Optional attached files */
|
|
2760
|
+
get files() {
|
|
2761
|
+
const raw = this._getRaw("files");
|
|
2762
|
+
if (!raw) return void 0;
|
|
2763
|
+
try {
|
|
2764
|
+
return JSON.parse(raw);
|
|
2765
|
+
} catch {
|
|
2766
|
+
return void 0;
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
/** Created timestamp */
|
|
2770
|
+
get createdAt() {
|
|
2771
|
+
return new Date(this._getRaw("created_at"));
|
|
2772
|
+
}
|
|
2773
|
+
/** Updated timestamp */
|
|
2774
|
+
get updatedAt() {
|
|
2775
|
+
return new Date(this._getRaw("updated_at"));
|
|
2776
|
+
}
|
|
2777
|
+
/** Embedding vector for semantic search */
|
|
2778
|
+
get vector() {
|
|
2779
|
+
const raw = this._getRaw("vector");
|
|
2780
|
+
if (!raw) return void 0;
|
|
2781
|
+
try {
|
|
2782
|
+
return JSON.parse(raw);
|
|
2783
|
+
} catch {
|
|
2784
|
+
return void 0;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
/** Model used to generate embedding */
|
|
2788
|
+
get embeddingModel() {
|
|
2789
|
+
const value = this._getRaw("embedding_model");
|
|
2790
|
+
return value ? value : void 0;
|
|
2791
|
+
}
|
|
2792
|
+
/** Token counts and cost */
|
|
2793
|
+
get usage() {
|
|
2794
|
+
const raw = this._getRaw("usage");
|
|
2795
|
+
if (!raw) return void 0;
|
|
2796
|
+
try {
|
|
2797
|
+
return JSON.parse(raw);
|
|
2798
|
+
} catch {
|
|
2799
|
+
return void 0;
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
/** Web search sources */
|
|
2803
|
+
get sources() {
|
|
2804
|
+
const raw = this._getRaw("sources");
|
|
2805
|
+
if (!raw) return void 0;
|
|
2806
|
+
try {
|
|
2807
|
+
return JSON.parse(raw);
|
|
2808
|
+
} catch {
|
|
2809
|
+
return void 0;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
/** Response time in seconds */
|
|
2813
|
+
get responseDuration() {
|
|
2814
|
+
const value = this._getRaw("response_duration");
|
|
2815
|
+
return value !== null && value !== void 0 ? value : void 0;
|
|
2816
|
+
}
|
|
2817
|
+
};
|
|
2818
|
+
Message.table = "history";
|
|
2819
|
+
Message.associations = {
|
|
2820
|
+
conversations: { type: "belongs_to", key: "conversation_id" }
|
|
2821
|
+
};
|
|
2822
|
+
var Conversation = class extends import_watermelondb4.Model {
|
|
2823
|
+
/** Unique conversation identifier */
|
|
2824
|
+
get conversationId() {
|
|
2825
|
+
return this._getRaw("conversation_id");
|
|
2826
|
+
}
|
|
2827
|
+
/** Conversation title */
|
|
2828
|
+
get title() {
|
|
2829
|
+
return this._getRaw("title");
|
|
2830
|
+
}
|
|
2831
|
+
/** Created timestamp */
|
|
2832
|
+
get createdAt() {
|
|
2833
|
+
return new Date(this._getRaw("created_at"));
|
|
2834
|
+
}
|
|
2835
|
+
/** Updated timestamp */
|
|
2836
|
+
get updatedAt() {
|
|
2837
|
+
return new Date(this._getRaw("updated_at"));
|
|
2838
|
+
}
|
|
2839
|
+
/** Soft delete flag */
|
|
2840
|
+
get isDeleted() {
|
|
2841
|
+
return this._getRaw("is_deleted");
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
Conversation.table = "conversations";
|
|
2845
|
+
Conversation.associations = {
|
|
2846
|
+
history: { type: "has_many", foreignKey: "conversation_id" }
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2849
|
+
// src/lib/memoryStorage/schema.ts
|
|
2850
|
+
var import_watermelondb5 = require("@nozbe/watermelondb");
|
|
2851
|
+
var memoryStorageSchema = (0, import_watermelondb5.appSchema)({
|
|
2852
|
+
version: 1,
|
|
2853
|
+
tables: [
|
|
2854
|
+
(0, import_watermelondb5.tableSchema)({
|
|
2855
|
+
name: "memories",
|
|
2856
|
+
columns: [
|
|
2857
|
+
// Memory type classification
|
|
2858
|
+
{ name: "type", type: "string", isIndexed: true },
|
|
2859
|
+
// 'identity' | 'preference' | 'project' | 'skill' | 'constraint'
|
|
2860
|
+
// Hierarchical key structure
|
|
2861
|
+
{ name: "namespace", type: "string", isIndexed: true },
|
|
2862
|
+
{ name: "key", type: "string", isIndexed: true },
|
|
2863
|
+
{ name: "value", type: "string" },
|
|
2864
|
+
// Evidence and confidence
|
|
2865
|
+
{ name: "raw_evidence", type: "string" },
|
|
2866
|
+
{ name: "confidence", type: "number" },
|
|
2867
|
+
{ name: "pii", type: "boolean", isIndexed: true },
|
|
2868
|
+
// Composite keys for efficient lookups
|
|
2869
|
+
{ name: "composite_key", type: "string", isIndexed: true },
|
|
2870
|
+
// namespace:key
|
|
2871
|
+
{ name: "unique_key", type: "string", isIndexed: true },
|
|
2872
|
+
// namespace:key:value
|
|
2873
|
+
// Timestamps
|
|
2874
|
+
{ name: "created_at", type: "number", isIndexed: true },
|
|
2875
|
+
{ name: "updated_at", type: "number" },
|
|
2876
|
+
// Vector embeddings for semantic search
|
|
2877
|
+
{ name: "embedding", type: "string", isOptional: true },
|
|
2878
|
+
// JSON stringified number[]
|
|
2879
|
+
{ name: "embedding_model", type: "string", isOptional: true },
|
|
2880
|
+
// Soft delete flag
|
|
2881
|
+
{ name: "is_deleted", type: "boolean", isIndexed: true }
|
|
2882
|
+
]
|
|
2883
|
+
})
|
|
2884
|
+
]
|
|
2885
|
+
});
|
|
2886
|
+
|
|
2887
|
+
// src/lib/memoryStorage/models.ts
|
|
2888
|
+
var import_watermelondb6 = require("@nozbe/watermelondb");
|
|
2889
|
+
var Memory = class extends import_watermelondb6.Model {
|
|
2890
|
+
/** Memory type classification */
|
|
2891
|
+
get type() {
|
|
2892
|
+
return this._getRaw("type");
|
|
2893
|
+
}
|
|
2894
|
+
/** Namespace for grouping related memories */
|
|
2895
|
+
get namespace() {
|
|
2896
|
+
return this._getRaw("namespace");
|
|
2897
|
+
}
|
|
2898
|
+
/** Key within the namespace */
|
|
2899
|
+
get key() {
|
|
2900
|
+
return this._getRaw("key");
|
|
2901
|
+
}
|
|
2902
|
+
/** The memory value/content */
|
|
2903
|
+
get value() {
|
|
2904
|
+
return this._getRaw("value");
|
|
2905
|
+
}
|
|
2906
|
+
/** Raw evidence from which this memory was extracted */
|
|
2907
|
+
get rawEvidence() {
|
|
2908
|
+
return this._getRaw("raw_evidence");
|
|
2909
|
+
}
|
|
2910
|
+
/** Confidence score (0-1) */
|
|
2911
|
+
get confidence() {
|
|
2912
|
+
return this._getRaw("confidence");
|
|
2913
|
+
}
|
|
2914
|
+
/** Whether this memory contains PII */
|
|
2915
|
+
get pii() {
|
|
2916
|
+
return this._getRaw("pii");
|
|
2917
|
+
}
|
|
2918
|
+
/** Composite key (namespace:key) for efficient lookups */
|
|
2919
|
+
get compositeKey() {
|
|
2920
|
+
return this._getRaw("composite_key");
|
|
2921
|
+
}
|
|
2922
|
+
/** Unique key (namespace:key:value) for deduplication */
|
|
2923
|
+
get uniqueKey() {
|
|
2924
|
+
return this._getRaw("unique_key");
|
|
2925
|
+
}
|
|
2926
|
+
/** Created timestamp */
|
|
2927
|
+
get createdAt() {
|
|
2928
|
+
return new Date(this._getRaw("created_at"));
|
|
2929
|
+
}
|
|
2930
|
+
/** Updated timestamp */
|
|
2931
|
+
get updatedAt() {
|
|
2932
|
+
return new Date(this._getRaw("updated_at"));
|
|
2933
|
+
}
|
|
2934
|
+
/** Embedding vector for semantic search */
|
|
2935
|
+
get embedding() {
|
|
2936
|
+
const raw = this._getRaw("embedding");
|
|
2937
|
+
if (!raw) return void 0;
|
|
2938
|
+
try {
|
|
2939
|
+
return JSON.parse(raw);
|
|
2940
|
+
} catch {
|
|
2941
|
+
return void 0;
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
/** Model used to generate embedding */
|
|
2945
|
+
get embeddingModel() {
|
|
2946
|
+
const value = this._getRaw("embedding_model");
|
|
2947
|
+
return value ? value : void 0;
|
|
2948
|
+
}
|
|
2949
|
+
/** Soft delete flag */
|
|
2950
|
+
get isDeleted() {
|
|
2951
|
+
return this._getRaw("is_deleted");
|
|
2952
|
+
}
|
|
2953
|
+
};
|
|
2954
|
+
Memory.table = "memories";
|
|
1324
2955
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1325
2956
|
0 && (module.exports = {
|
|
2957
|
+
ChatConversation,
|
|
2958
|
+
ChatMessage,
|
|
2959
|
+
StoredMemoryModel,
|
|
2960
|
+
chatStorageSchema,
|
|
2961
|
+
generateCompositeKey,
|
|
2962
|
+
generateConversationId,
|
|
2963
|
+
generateUniqueKey,
|
|
2964
|
+
memoryStorageSchema,
|
|
1326
2965
|
useChat,
|
|
2966
|
+
useChatStorage,
|
|
1327
2967
|
useImageGeneration,
|
|
2968
|
+
useMemoryStorage,
|
|
1328
2969
|
useModels
|
|
1329
2970
|
});
|