fix: resolve Prisma 'vector' deserialization error and missing PDF canvas dependency
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user