Files
ChatLab/electron/main/nlp/ftsTokenizer.ts
T

95 lines
2.6 KiB
TypeScript

/**
* FTS5 专用分词器
*
* 与 NLP 词频分析不同,FTS 分词不做词性过滤和停用词过滤,
* 搜索场景需要保留所有词以保证召回率。
*
* 使用 jieba 处理中文(天然兼容中英混合文本),
* Intl.Segmenter 处理纯英文/日文。
*
* 复用 segmenter 模块的 jieba 实例池,默认使用 zh-CN 词库。
*/
import { getJieba } from './segmenter'
/**
* 对文本进行 FTS 分词,返回空格分隔的 token 字符串。
* 用于写入 FTS5 索引。
*/
export function tokenizeForFts(text: string | null | undefined): string {
if (!text || text.trim().length === 0) return ''
try {
const jieba = getJieba()
const tokens = jieba.cut(text, false)
return tokens
.map((t) => t.trim().toLowerCase())
.filter((t) => t.length > 0)
.join(' ')
} catch {
return fallbackTokenize(text)
}
}
/**
* 降级分词:jieba 不可用时按字符/空格切分
*/
function fallbackTokenize(text: string): string {
return text
.toLowerCase()
.split(/\s+/)
.filter((t) => t.length > 0)
.join(' ')
}
/**
* 转义单个 token 使其在 FTS5 MATCH 中安全使用。
* 用双引号包裹并转义内部双引号。
*/
function escapeToken(token: string): string {
return `"${token.replace(/"/g, '""')}"`
}
/**
* 将用户搜索关键词列表转换为 FTS5 MATCH 表达式。
*
* 语义:
* - 单个关键词先分词,分词结果之间为 AND(都要出现)
* - 多个关键词之间为 OR(任一匹配即可)
*
* 示例:
* - ["今天开心"] -> '"今天" "开心"' (AND)
* - ["今天开心", "难过"] -> '("今天" "开心") OR "难过"' (OR)
* - ["hello"] -> '"hello"'
*/
export function tokenizeQueryForFts(keywords: string[]): string {
if (keywords.length === 0) return ''
const groups = keywords
.map((kw) => {
const trimmed = kw.trim()
if (!trimmed) return ''
try {
const jieba = getJieba()
const tokens = jieba
.cut(trimmed, false)
.map((t) => t.trim().toLowerCase())
.filter((t) => t.length > 0)
if (tokens.length === 0) return ''
if (tokens.length === 1) return escapeToken(tokens[0])
return tokens.map(escapeToken).join(' ')
} catch {
const simple = trimmed.toLowerCase().trim()
return simple ? escapeToken(simple) : ''
}
})
.filter((g) => g.length > 0)
if (groups.length === 0) return ''
if (groups.length === 1) return groups[0]
return groups.map((g) => (g.includes(' ') ? `(${g})` : g)).join(' OR ')
}