import { uniqueSorted } from './util.ts'
export const NOTE_KINDS = [
'note',
'reminder',
'contact',
'instruction',
'fact',
] as const
export type NoteKind = (typeof NOTE_KINDS)[number]
export function inferKind(text: string): NoteKind {
const lower = text.toLowerCase()
if (/\b(remind|todo|to-do|to do|need to|follow up|deadline)\b/.test(lower)) {
return 'reminder'
}
if (/\b(phone|cell|mobile|call|text|email|@)\b/.test(lower)) return 'contact'
if (
/\b(instruction|instructions|how to|steps|setup|configure|recipe)\b/.test(
lower,
)
) {
return 'instruction'
}
if (
/\b(watt|watts|volt|volts|amps|amp|inch|inches|feet|ft|lbs|pounds|psi|serial|model)\b/.test(
lower,
) ||
/\b\d+\s*w\b/.test(lower)
) {
return 'fact'
}
return 'note'
}
export function summarize(text: string): string {
const firstLine =
text
.split(/\r?\n/)
.map((line) => line.trim())
.find(Boolean) ?? text.trim()
return firstLine.length <= 100
? firstLine
: firstLine.slice(0, 97).trimEnd() + '...'
}
export function extractTags(
text: string,
explicitTags: unknown,
kind: string,
): string[] {
const hashTags = Array.from(
text.matchAll(/(?:^|\s)#([a-zA-Z0-9_-]{2,40})/g),
(match) => match[1] ?? '',
)
const inferred = [kind]
const lower = text.toLowerCase()
if (lower.includes('watt') || /\b\d+\s*w\b/.test(lower)) {
inferred.push('wattage')
}
if (lower.includes('phone') || /\b\+?\d[\d().\-\s]{6,}\d\b/.test(text)) {
inferred.push('contact')
}
if (lower.includes('remind')) inferred.push('reminder')
const explicit = Array.isArray(explicitTags)
? explicitTags.filter((tag): tag is string => typeof tag === 'string')
: []
return uniqueSorted([...explicit, ...hashTags, ...inferred])
}
export function extractEntities(text: string): string[] {
const phoneNumbers = Array.from(
text.matchAll(/\b\+?\d[\d().\-\s]{6,}\d\b/g),
(match) => match[0]?.trim() ?? '',
)
const emails = Array.from(
text.matchAll(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi),
(match) => match[0]?.trim() ?? '',
)
const titlePhrases = Array.from(
text.matchAll(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+){0,3}\b/g),
(match) => match[0]?.trim() ?? '',
)
return uniqueSorted([...phoneNumbers, ...emails, ...titlePhrases].slice(0, 20))
}
export function scoreHaystack(haystack: string, query: string): number {
const normalizedQuery = query.trim().toLowerCase()
if (!normalizedQuery) return 0.1
const normalizedHaystack = haystack.toLowerCase()
const terms = normalizedQuery.split(/\s+/).filter(Boolean)
const matchedTerms = terms.filter((term) =>
normalizedHaystack.includes(term),
).length
const exactBoost = normalizedHaystack.includes(normalizedQuery) ? 0.45 : 0
return Math.min(1, exactBoost + matchedTerms / Math.max(terms.length, 1))
}