feat(graph): improve note graph relationships by integrating wikilinks and semantic AI echo insights
This commit is contained in:
@@ -36,7 +36,7 @@ function jaccardSimilarity(a: Set<string>, b: Set<string>): number {
|
||||
return intersection / (a.size + b.size - intersection)
|
||||
}
|
||||
|
||||
type EdgeType = 'title_mention' | 'shared_label' | 'jaccard'
|
||||
type EdgeType = 'title_mention' | 'shared_label' | 'jaccard' | 'explicit_link' | 'semantic_echo'
|
||||
|
||||
interface GraphEdge { source: string; target: string; weight: number; type: EdgeType }
|
||||
|
||||
@@ -62,6 +62,35 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const ids = notes.map(n => n.id)
|
||||
|
||||
// Query NoteLink manually created relationships
|
||||
const noteLinks = await (prisma as any).noteLink.findMany({
|
||||
where: {
|
||||
sourceNoteId: { in: ids },
|
||||
targetNoteId: { in: ids }
|
||||
},
|
||||
select: {
|
||||
sourceNoteId: true,
|
||||
targetNoteId: true,
|
||||
contextSnippet: true
|
||||
}
|
||||
})
|
||||
|
||||
// Query MemoryEchoInsight semantic relationships
|
||||
const echoInsights = await (prisma as any).memoryEchoInsight.findMany({
|
||||
where: {
|
||||
userId,
|
||||
dismissed: false,
|
||||
note1Id: { in: ids },
|
||||
note2Id: { in: ids }
|
||||
},
|
||||
select: {
|
||||
note1Id: true,
|
||||
note2Id: true,
|
||||
similarityScore: true,
|
||||
insight: true
|
||||
}
|
||||
})
|
||||
|
||||
// Pré-calcul
|
||||
const keywordsMap = new Map<string, Set<string>>()
|
||||
const labelMap = new Map<string, Set<string>>()
|
||||
@@ -70,11 +99,27 @@ export async function GET(request: NextRequest) {
|
||||
labelMap.set(note.id, new Set(note.labelRelations.map((l: any) => l.id)))
|
||||
}
|
||||
|
||||
const EDGE_TYPE_PRIORITY: Record<EdgeType, number> = {
|
||||
explicit_link: 5,
|
||||
semantic_echo: 4,
|
||||
title_mention: 3,
|
||||
shared_label: 2,
|
||||
jaccard: 1,
|
||||
}
|
||||
|
||||
const edgeMap = new Map<string, GraphEdge>()
|
||||
function upsertEdge(a: string, b: string, weight: number, type: EdgeType) {
|
||||
const key = a < b ? `${a}--${b}` : `${b}--${a}`
|
||||
const ex = edgeMap.get(key)
|
||||
if (!ex || ex.weight < weight) edgeMap.set(key, { source: a < b ? a : b, target: a < b ? b : a, weight, type })
|
||||
if (!ex) {
|
||||
edgeMap.set(key, { source: a < b ? a : b, target: a < b ? b : a, weight, type })
|
||||
} else {
|
||||
const exPriority = EDGE_TYPE_PRIORITY[ex.type] || 0
|
||||
const curPriority = EDGE_TYPE_PRIORITY[type] || 0
|
||||
if (curPriority > exPriority || (curPriority === exPriority && weight > ex.weight)) {
|
||||
edgeMap.set(key, { source: a < b ? a : b, target: a < b ? b : a, weight, type })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Niveau 1 : Title Mention (comme Obsidian "unlinked mentions") ──────────
|
||||
@@ -113,6 +158,16 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Niveau 4 : WikiLinks explicites (NoteLink) ─────────────────────────────
|
||||
for (const link of noteLinks) {
|
||||
upsertEdge(link.sourceNoteId, link.targetNoteId, 1.0, 'explicit_link')
|
||||
}
|
||||
|
||||
// ── Niveau 5 : Échos sémantiques IA (MemoryEchoInsight) ────────────────────
|
||||
for (const echo of echoInsights) {
|
||||
upsertEdge(echo.note1Id, echo.note2Id, echo.similarityScore, 'semantic_echo')
|
||||
}
|
||||
|
||||
const degreeMap = new Map<string, number>()
|
||||
for (const e of edgeMap.values()) {
|
||||
degreeMap.set(e.source, (degreeMap.get(e.source) ?? 0) + 1)
|
||||
|
||||
Reference in New Issue
Block a user