This commit is contained in:
2026-03-22 13:01:46 -03:00
parent af0910f428
commit 6694bce736
52 changed files with 4949 additions and 102 deletions

81
src/lib/related.ts Normal file
View File

@@ -0,0 +1,81 @@
import { prisma } from '@/lib/prisma'
interface ScoredNote {
id: string
title: string
type: string
tags: string[]
score: number
reason: string
}
export async function getRelatedNotes(noteId: string, limit = 5): Promise<ScoredNote[]> {
const note = await prisma.note.findUnique({
where: { id: noteId },
include: { tags: { include: { tag: true } } },
})
if (!note) return []
const noteTagNames = note.tags.map(t => t.tag.name)
const noteWords = note.title.toLowerCase().split(/\s+/).filter(w => w.length > 2)
const noteContentWords = note.content.toLowerCase().split(/\s+/).filter(w => w.length > 4)
const allNotes = await prisma.note.findMany({
where: { id: { not: noteId } },
include: { tags: { include: { tag: true } } },
})
const scored: ScoredNote[] = []
for (const other of allNotes) {
let score = 0
const reasons: string[] = []
// +3 si comparten tipo
if (other.type === note.type) {
score += 3
reasons.push(`Same type (${note.type})`)
}
// +2 por cada tag compartido
const sharedTags = noteTagNames.filter(t => other.tags.some(ot => ot.tag.name === t))
score += sharedTags.length * 2
if (sharedTags.length > 0) {
reasons.push(`Shared tags: ${sharedTags.join(', ')}`)
}
// +1 por palabra relevante compartida en título
const sharedTitleWords = noteWords.filter(w =>
other.title.toLowerCase().includes(w)
)
score += Math.min(sharedTitleWords.length, 2) // max +2
if (sharedTitleWords.length > 0) {
reasons.push(`Title match: ${sharedTitleWords.slice(0, 2).join(', ')}`)
}
// +1 si keyword del contenido aparece en ambas
const sharedContentWords = noteContentWords.filter(w =>
other.content.toLowerCase().includes(w)
)
score += Math.min(sharedContentWords.length, 2) // max +2
if (sharedContentWords.length > 0) {
reasons.push(`Content: ${sharedContentWords.slice(0, 2).join(', ')}`)
}
if (score > 0) {
scored.push({
id: other.id,
title: other.title,
type: other.type,
tags: other.tags.map(t => t.tag.name),
score,
reason: reasons.join(' | '),
})
}
}
return scored
.sort((a, b) => b.score - a.score)
.slice(0, limit)
}