package search // containsCJK 是否含 CJK 统一表意文字 func containsCJK(s string) bool { for _, r := range s { if r >= 0x4E00 && r <= 0x9FFF { return true } } return false } // ExpandCJKKeywords 关键词展开(search 与 explore 共享):CJK 长词(>3 字符) // 拆成 bigram 滑动窗口(FTS5 unicode61 把连续中文当整串单 token,长复合词整串 // LIKE 匹配不到;文档里 "电子秤补气失败" 通常不是连续子串,bigram 才能命中)。 // 原词保留(段落命中判断时原词更精确)。末尾去重保序(原词在前、展开词在后)。 func ExpandCJKKeywords(keywords []string) []string { var out []string for _, kw := range keywords { out = append(out, kw) if containsCJK(kw) && len([]rune(kw)) > 3 { runes := []rune(kw) for i := 0; i+1 < len(runes); i++ { bg := string(runes[i : i+2]) out = append(out, bg) } } } // 去重保序(原词在前、展开词在后):避免 bigram 与原词重复时 text 分重复计权 var dedup []string seen := make(map[string]bool, len(out)) for _, kw := range out { if !seen[kw] { seen[kw] = true dedup = append(dedup, kw) } } return dedup }