spb/note-spboucher-ai
Public
1/** Sentence-level "unique to this answer" diff using word-set overlap (no dependency). */2const STOP = new Set(["the", "a", "an", "and", "or", "of", "to", "in", "on", "for", "is", "are", "with", "that", "this", "it", "as", "be", "by", "at", "le", "la", "les", "de", "des", "du", "un", "une", "et", "en", "est", "que", "qui", "pour", "dans", "sur", "au", "aux"]);3export function sentences(text: string): string[] { return text.replace(/```[\s\S]*?```/g, (m) => m.replace(/\n/g, " ")).split(/(?<=[.!?:;])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length > 0); }4export function words(s: string): Set<string> { return new Set((s.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? []).filter((w) => w.length > 2 && !STOP.has(w))); }5function overlap(a: Set<string>, b: Set<string>) { if (!a.size || !b.size) return 0; let n = 0; for (const w of a) if (b.has(w)) n++; return n / Math.min(a.size, b.size); }6/** Returns, for `text`, each sentence with `unique = true` when no sentence of any `others` shares ≥ 60 % of its content words. */7export function uniqueSentences(text: string, others: string[], threshold = 0.4): { s: string; unique: boolean }[] {8 const otherSets = others.flatMap((o) => sentences(o).map(words)).filter((w) => w.size > 0);9 return sentences(text).map((s) => { const w = words(s); if (w.size < 2) return { s, unique: false }; return { s, unique: !otherSets.some((o) => overlap(w, o) >= threshold) }; });10}11