From 5a2be87f658d01bc5b3b7ecdba92ac69bf09dcf7 Mon Sep 17 00:00:00 2001
From: ai_xiaopei <xiaopei@aisim.cn>
Date: Thu, 03 Sep 2026 15:15:37 +0800
Subject: [PATCH] fix(search): 搜索准确性四连修——FTS5 MATCH 引号包裹(连字符不再被解析成 MINUS);KeywordSearch 截断按命中关键词数排序(高频 bigram 不再挤出强相关文档);原词奖励档(完整短语压过泛化 bigram);tags 字段真实参与评分(此前误用 Section)

---
 internal/search/expand.go |   39 +++++++++++++++++++++++++++++++++++++++
 1 files changed, 39 insertions(+), 0 deletions(-)

diff --git a/internal/search/expand.go b/internal/search/expand.go
new file mode 100644
index 0000000..1c1d835
--- /dev/null
+++ b/internal/search/expand.go
@@ -0,0 +1,39 @@
+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
+}

--
Gitblit v1.10.0