feat(db): extraction des embeddings + mode WAL + config DB provider-agnostic
- Ajout de la table de relation 1-1 NoteEmbedding pour alléger Model Note - Refactor complet des actions IA sémantique et Memory Echo pour utiliser la jointure - Migration propre des 85 embeddings locaux existants - Ajout PRAGMA journal_mode=WAL pour la concurrence au sein de lib/prisma - Ajout npm run db:switch pour configuration auto SQLite / PostgreSQL - Fix du compilateur Turbopack et Next-PWA
This commit is contained in:
@@ -61,13 +61,13 @@ export class MemoryEchoService {
|
||||
where: {
|
||||
userId,
|
||||
isArchived: false,
|
||||
embedding: { not: null } // Only notes with embeddings
|
||||
noteEmbedding: { isNot: null } // Only notes with embeddings
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
content: true,
|
||||
embedding: true,
|
||||
noteEmbedding: true,
|
||||
createdAt: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
@@ -81,7 +81,7 @@ export class MemoryEchoService {
|
||||
const notesWithEmbeddings = notes
|
||||
.map(note => ({
|
||||
...note,
|
||||
embedding: note.embedding ? JSON.parse(note.embedding) as number[] : null
|
||||
embedding: note.noteEmbedding?.embedding ? JSON.parse(note.noteEmbedding.embedding) as number[] : null
|
||||
}))
|
||||
.filter(note => note.embedding && Array.isArray(note.embedding))
|
||||
|
||||
@@ -367,7 +367,7 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu
|
||||
id: true,
|
||||
title: true,
|
||||
content: true,
|
||||
embedding: true,
|
||||
noteEmbedding: true,
|
||||
createdAt: true,
|
||||
userId: true
|
||||
}
|
||||
@@ -377,7 +377,7 @@ 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.embedding) {
|
||||
if (!targetNote.noteEmbedding) {
|
||||
return [] // Note has no embedding
|
||||
}
|
||||
|
||||
@@ -408,15 +408,15 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu
|
||||
const otherNotes = await prisma.note.findMany({
|
||||
where: {
|
||||
userId,
|
||||
id: { not: noteId }, // Exclude the target note
|
||||
id: { not: noteId },
|
||||
isArchived: false,
|
||||
embedding: { not: null }
|
||||
noteEmbedding: { isNot: null }
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
content: true,
|
||||
embedding: true,
|
||||
noteEmbedding: true,
|
||||
createdAt: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
@@ -427,7 +427,7 @@ Explain in one brief sentence (max 15 words) why these notes are connected. Focu
|
||||
}
|
||||
|
||||
// Target note embedding (already native Json from PostgreSQL)
|
||||
const targetEmbedding = targetNote.embedding ? JSON.parse(targetNote.embedding) as number[] : null
|
||||
const targetEmbedding = targetNote.noteEmbedding?.embedding ? JSON.parse(targetNote.noteEmbedding.embedding) as number[] : null
|
||||
if (!targetEmbedding) return []
|
||||
|
||||
// Check if user has demo mode enabled
|
||||
@@ -443,9 +443,9 @@ 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.embedding) continue
|
||||
if (!otherNote.noteEmbedding) continue
|
||||
|
||||
const otherEmbedding = otherNote.embedding ? JSON.parse(otherNote.embedding) as number[] : null
|
||||
const otherEmbedding = otherNote.noteEmbedding?.embedding ? JSON.parse(otherNote.noteEmbedding.embedding) as number[] : null
|
||||
if (!otherEmbedding) continue
|
||||
|
||||
// Check if this connection was dismissed
|
||||
|
||||
@@ -177,12 +177,12 @@ export class SemanticSearchService {
|
||||
const notes = await prisma.note.findMany({
|
||||
where: {
|
||||
...(userId ? { userId } : {}),
|
||||
...(notebookId !== undefined ? { notebookId } : {}), // NEW: Notebook filter
|
||||
embedding: { not: null }
|
||||
...(notebookId !== undefined ? { notebookId } : {}),
|
||||
noteEmbedding: { isNot: null }
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
embedding: true
|
||||
noteEmbedding: true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -192,7 +192,7 @@ export class SemanticSearchService {
|
||||
|
||||
// Calculate similarities for all notes
|
||||
const similarities = notes.map(note => {
|
||||
const noteEmbedding = note.embedding ? JSON.parse(note.embedding) as number[] : []
|
||||
const noteEmbedding = note.noteEmbedding?.embedding ? JSON.parse(note.noteEmbedding.embedding) as number[] : []
|
||||
const similarity = embeddingService.calculateCosineSimilarity(
|
||||
queryEmbedding,
|
||||
noteEmbedding
|
||||
@@ -273,7 +273,7 @@ export class SemanticSearchService {
|
||||
try {
|
||||
const note = await prisma.note.findUnique({
|
||||
where: { id: noteId },
|
||||
select: { content: true, embedding: true, lastAiAnalysis: true }
|
||||
select: { content: true, noteEmbedding: true, lastAiAnalysis: true }
|
||||
})
|
||||
|
||||
if (!note) {
|
||||
@@ -283,7 +283,7 @@ export class SemanticSearchService {
|
||||
// Check if embedding needs regeneration
|
||||
const shouldRegenerate = embeddingService.shouldRegenerateEmbedding(
|
||||
note.content,
|
||||
note.embedding as any,
|
||||
note.noteEmbedding?.embedding as any,
|
||||
note.lastAiAnalysis
|
||||
)
|
||||
|
||||
@@ -295,10 +295,14 @@ export class SemanticSearchService {
|
||||
const { embedding } = await embeddingService.generateEmbedding(note.content)
|
||||
|
||||
// Save to database
|
||||
await prisma.noteEmbedding.upsert({
|
||||
where: { noteId: noteId },
|
||||
create: { noteId: noteId, embedding: embeddingService.serialize(embedding) as any },
|
||||
update: { embedding: embeddingService.serialize(embedding) as any }
|
||||
})
|
||||
await prisma.note.update({
|
||||
where: { id: noteId },
|
||||
data: {
|
||||
embedding: embeddingService.serialize(embedding) as any,
|
||||
lastAiAnalysis: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user