diff --git a/memento-note/lib/ai/services/memory-echo.service.ts b/memento-note/lib/ai/services/memory-echo.service.ts index 215a583..2feb76e 100644 --- a/memento-note/lib/ai/services/memory-echo.service.ts +++ b/memento-note/lib/ai/services/memory-echo.service.ts @@ -111,7 +111,7 @@ export class MemoryEchoService { userId, isArchived: false, trashedAt: null, - noteEmbedding: { isNot: null } // Only notes with embeddings + // noteEmbedding: { isNot: null } // Removed because Unsupported type cannot be used in 'where' easily }, select: { id: true, @@ -127,11 +127,20 @@ export class MemoryEchoService { return [] // Need at least 2 notes to find connections } + if (notes.length < 2) return [] + + // Fetch embeddings separately using raw SQL to avoid deserialization error + const noteIds = notes.map(n => n.id) + const embeddings: Array<{ noteId: string, embedding: any }> = await prisma.$queryRawUnsafe( + `SELECT "noteId", "embedding"::text FROM "NoteEmbedding" WHERE "noteId" IN (${noteIds.map(id => `'${id}'`).join(',')})` + ) + const embeddingMap = new Map(embeddings.map(e => [e.noteId, e.embedding])) + const notesWithEmbeddings = notes .map(note => ({ ...note, - embedding: note.noteEmbedding?.embedding - ? embeddingService.fromVectorString(note.noteEmbedding.embedding as unknown as string) + embedding: embeddingMap.has(note.id) + ? embeddingService.fromVectorString(embeddingMap.get(note.id)) : null })) .filter(note => note.embedding && Array.isArray(note.embedding)) @@ -446,7 +455,6 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu id: true, title: true, content: true, - noteEmbedding: true, createdAt: true, userId: true } @@ -456,7 +464,14 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu return [] // Note not found or doesn't belong to user } - if (!targetNote.noteEmbedding) { + // Fetch embedding separately + const embeddingResult: Array<{ embedding: any }> = await prisma.$queryRawUnsafe( + `SELECT "embedding"::text FROM "NoteEmbedding" WHERE "noteId" = $1`, + noteId + ) + const targetEmbeddingStr = embeddingResult[0]?.embedding + + if (!targetEmbeddingStr) { return [] // Note has no embedding } @@ -490,13 +505,11 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu id: { not: noteId }, isArchived: false, trashedAt: null, - noteEmbedding: { isNot: null } }, select: { id: true, title: true, content: true, - noteEmbedding: true, createdAt: true }, orderBy: { createdAt: 'desc' } @@ -506,11 +519,18 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu return [] } - const targetEmbedding = targetNote.noteEmbedding?.embedding - ? embeddingService.fromVectorString(targetNote.noteEmbedding.embedding as unknown as string) + const targetEmbedding = targetEmbeddingStr + ? embeddingService.fromVectorString(targetEmbeddingStr) : null if (!targetEmbedding) return [] + // Fetch all other embeddings + const otherNoteIds = otherNotes.map(n => n.id) + const otherEmbeddings: Array<{ noteId: string, embedding: any }> = await prisma.$queryRawUnsafe( + `SELECT "noteId", "embedding"::text FROM "NoteEmbedding" WHERE "noteId" IN (${otherNoteIds.map(id => `'${id}'`).join(',')})` + ) + const otherEmbeddingMap = new Map(otherEmbeddings.map(e => [e.noteId, e.embedding])) + // Check if user has demo mode enabled const settings = await prisma.userAISettings.findUnique({ where: { userId } @@ -540,11 +560,10 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu // Compare target note with all other notes for (const otherNote of otherNotes) { - if (!otherNote.noteEmbedding) continue + const otherEmbeddingStr = otherEmbeddingMap.get(otherNote.id) + if (!otherEmbeddingStr) continue - const otherEmbedding = otherNote.noteEmbedding?.embedding - ? embeddingService.fromVectorString(otherNote.noteEmbedding.embedding as unknown as string) - : null + const otherEmbedding = embeddingService.fromVectorString(otherEmbeddingStr) if (!otherEmbedding) continue // Check if this connection was dismissed diff --git a/memento-note/package.json b/memento-note/package.json index 1fb8fd0..7f06cc0 100644 --- a/memento-note/package.json +++ b/memento-note/package.json @@ -120,7 +120,8 @@ "tailwind-merge": "^3.4.0", "tinyld": "^1.3.4", "vazirmatn": "^33.0.3", - "zod": "^4.3.5" + "zod": "^4.3.5", + "@napi-rs/canvas": "^0.1.65" }, "devDependencies": { "@playwright/test": "^1.57.0", diff --git a/memento-note/prisma/schema.prisma b/memento-note/prisma/schema.prisma index d9ac59e..103d559 100644 --- a/memento-note/prisma/schema.prisma +++ b/memento-note/prisma/schema.prisma @@ -1,11 +1,12 @@ generator client { - provider = "prisma-client-js" - binaryTargets = ["debian-openssl-3.0.x", "native"] + provider = "prisma-client-js" + previewFeatures = ["debian-openssl-3.0.x", "native", "postgresqlExtensions"] } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") + extensions = [pgvector] } model User { @@ -315,8 +316,8 @@ model UserAISettings { model NoteEmbedding { id String @id @default(cuid()) - noteId String @unique - embedding String + noteId String @unique + embedding Unsupported("vector(1536)")? createdAt DateTime @default(now()) note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)