fix: resolve Prisma 'vector' deserialization error and missing PDF canvas dependency
Some checks failed
CI / Lint, Test & Build (push) Failing after 5m29s
CI / Deploy production (on server) (push) Has been cancelled

This commit is contained in:
Antigravity
2026-05-19 21:34:50 +00:00
parent 37d9bea7bb
commit b935466480
3 changed files with 41 additions and 20 deletions

View File

@@ -111,7 +111,7 @@ export class MemoryEchoService {
userId, userId,
isArchived: false, isArchived: false,
trashedAt: null, trashedAt: null,
noteEmbedding: { isNot: null } // Only notes with embeddings // noteEmbedding: { isNot: null } // Removed because Unsupported type cannot be used in 'where' easily
}, },
select: { select: {
id: true, id: true,
@@ -127,11 +127,20 @@ export class MemoryEchoService {
return [] // Need at least 2 notes to find connections 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 const notesWithEmbeddings = notes
.map(note => ({ .map(note => ({
...note, ...note,
embedding: note.noteEmbedding?.embedding embedding: embeddingMap.has(note.id)
? embeddingService.fromVectorString(note.noteEmbedding.embedding as unknown as string) ? embeddingService.fromVectorString(embeddingMap.get(note.id))
: null : null
})) }))
.filter(note => note.embedding && Array.isArray(note.embedding)) .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, id: true,
title: true, title: true,
content: true, content: true,
noteEmbedding: true,
createdAt: true, createdAt: true,
userId: 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 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 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 }, id: { not: noteId },
isArchived: false, isArchived: false,
trashedAt: null, trashedAt: null,
noteEmbedding: { isNot: null }
}, },
select: { select: {
id: true, id: true,
title: true, title: true,
content: true, content: true,
noteEmbedding: true,
createdAt: true createdAt: true
}, },
orderBy: { createdAt: 'desc' } orderBy: { createdAt: 'desc' }
@@ -506,11 +519,18 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu
return [] return []
} }
const targetEmbedding = targetNote.noteEmbedding?.embedding const targetEmbedding = targetEmbeddingStr
? embeddingService.fromVectorString(targetNote.noteEmbedding.embedding as unknown as string) ? embeddingService.fromVectorString(targetEmbeddingStr)
: null : null
if (!targetEmbedding) return [] 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 // Check if user has demo mode enabled
const settings = await prisma.userAISettings.findUnique({ const settings = await prisma.userAISettings.findUnique({
where: { userId } 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 // Compare target note with all other notes
for (const otherNote of otherNotes) { for (const otherNote of otherNotes) {
if (!otherNote.noteEmbedding) continue const otherEmbeddingStr = otherEmbeddingMap.get(otherNote.id)
if (!otherEmbeddingStr) continue
const otherEmbedding = otherNote.noteEmbedding?.embedding const otherEmbedding = embeddingService.fromVectorString(otherEmbeddingStr)
? embeddingService.fromVectorString(otherNote.noteEmbedding.embedding as unknown as string)
: null
if (!otherEmbedding) continue if (!otherEmbedding) continue
// Check if this connection was dismissed // Check if this connection was dismissed

View File

@@ -120,7 +120,8 @@
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"tinyld": "^1.3.4", "tinyld": "^1.3.4",
"vazirmatn": "^33.0.3", "vazirmatn": "^33.0.3",
"zod": "^4.3.5" "zod": "^4.3.5",
"@napi-rs/canvas": "^0.1.65"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.57.0", "@playwright/test": "^1.57.0",

View File

@@ -1,11 +1,12 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
binaryTargets = ["debian-openssl-3.0.x", "native"] previewFeatures = ["debian-openssl-3.0.x", "native", "postgresqlExtensions"]
} }
datasource db { datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
extensions = [pgvector]
} }
model User { model User {
@@ -315,8 +316,8 @@ model UserAISettings {
model NoteEmbedding { model NoteEmbedding {
id String @id @default(cuid()) id String @id @default(cuid())
noteId String @unique noteId String @unique
embedding String embedding Unsupported("vector(1536)")?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)