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:
Sepehr Ramezani
2026-04-17 22:05:19 +02:00
parent a57c277168
commit 3ef5915062
10 changed files with 180 additions and 36 deletions

View File

@@ -349,8 +349,9 @@ async function semanticSearch(query: string, userId: string, notebookId?: string
where: {
userId: userId,
isArchived: false,
...(notebookId !== undefined ? { notebookId } : {}) // NEW: Filter by notebook (IA5)
}
...(notebookId !== undefined ? { notebookId } : {})
},
include: { noteEmbedding: true }
});
const queryLower = query.toLowerCase().trim();
@@ -380,8 +381,8 @@ async function semanticSearch(query: string, userId: string, notebookId?: string
// Semantic match (if embedding available)
let semanticMatch = false;
let similarity = 0;
if (queryEmbedding && note.embedding) {
similarity = cosineSimilarity(queryEmbedding, JSON.parse(note.embedding));
if (queryEmbedding && note.noteEmbedding?.embedding) {
similarity = cosineSimilarity(queryEmbedding, JSON.parse(note.noteEmbedding.embedding));
semanticMatch = similarity > 0.3; // 30% threshold - works well for related concepts
}
@@ -450,7 +451,6 @@ export async function createNote(data: {
reminder: data.reminder || null,
isMarkdown: data.isMarkdown || false,
size: data.size || 'small',
embedding: null, // Generated in background
autoGenerated: data.autoGenerated || null,
notebookId: data.notebookId || null,
}
@@ -480,9 +480,10 @@ export async function createNote(data: {
const provider = getAIProvider(await getSystemConfig())
const embedding = await provider.getEmbeddings(content)
if (embedding) {
await prisma.note.update({
where: { id: noteId },
data: { embedding: JSON.stringify(embedding) }
await prisma.noteEmbedding.upsert({
where: { noteId: noteId },
create: { noteId: noteId, embedding: JSON.stringify(embedding) },
update: { embedding: JSON.stringify(embedding) }
})
}
} catch (e) {
@@ -579,9 +580,10 @@ export async function updateNote(id: string, data: {
const provider = getAIProvider(await getSystemConfig());
const embedding = await provider.getEmbeddings(content);
if (embedding) {
await prisma.note.update({
where: { id: noteId },
data: { embedding: JSON.stringify(embedding) }
await prisma.noteEmbedding.upsert({
where: { noteId: noteId },
create: { noteId: noteId, embedding: JSON.stringify(embedding) },
update: { embedding: JSON.stringify(embedding) }
})
}
} catch (e) {
@@ -863,14 +865,23 @@ export async function syncAllEmbeddings() {
const userId = session.user.id;
let updatedCount = 0;
try {
const notesToSync = await prisma.note.findMany({ where: { userId, embedding: null } })
const notesToSync = await prisma.note.findMany({
where: {
userId,
noteEmbedding: { is: null }
}
})
const provider = getAIProvider(await getSystemConfig());
for (const note of notesToSync) {
if (!note.content) continue;
try {
const embedding = await provider.getEmbeddings(note.content);
if (embedding) {
await prisma.note.update({ where: { id: note.id }, data: { embedding: JSON.stringify(embedding) } })
await prisma.noteEmbedding.upsert({
where: { noteId: note.id },
create: { noteId: note.id, embedding: JSON.stringify(embedding) },
update: { embedding: JSON.stringify(embedding) }
})
updatedCount++;
}
} catch (e) { }

View File

@@ -29,7 +29,7 @@ export async function GET() {
select: {
id: true,
title: true,
embedding: true
noteEmbedding: true
}
})
@@ -45,7 +45,7 @@ export async function GET() {
for (const note of allNotes) {
// Check if embedding is missing
if (!note.embedding) {
if (!note.noteEmbedding?.embedding) {
missingCount++
invalidNotes.push({
id: note.id,
@@ -57,8 +57,8 @@ export async function GET() {
// Validate embedding
try {
if (!note.embedding) continue
const embedding = JSON.parse(note.embedding) as number[]
if (!note.noteEmbedding?.embedding) continue
const embedding = JSON.parse(note.noteEmbedding.embedding) as number[]
const validation = validateEmbedding(embedding)
if (!validation.valid) {