package search
|
|
import (
|
"sort"
|
"strings"
|
|
"github.com/aisim/kb-cli/internal/index"
|
)
|
|
// SearchOptions 搜索选项
|
type SearchOptions struct {
|
Expanded []string // 扩展词
|
Symptom []string // 症状词
|
TopN int // 返回前 N 条
|
}
|
|
// SearchResult 搜索结果
|
type SearchResult struct {
|
ID int64
|
Path string
|
Title string
|
Section string
|
Score int
|
}
|
|
// Search 执行搜索
|
func Search(store *index.Store, keywords []string, opts SearchOptions) ([]SearchResult, error) {
|
// 合并所有关键词
|
allKeywords := append(keywords, opts.Expanded...)
|
allKeywords = append(allKeywords, opts.Symptom...)
|
|
// FTS5 搜索
|
ftsResults, err := store.FTSSearch(allKeywords, 100)
|
if err != nil {
|
return nil, err
|
}
|
|
// 评分
|
scoreMap := make(map[int64]int)
|
for _, r := range ftsResults {
|
score := 0
|
|
// 关键词匹配评分
|
for _, kw := range keywords {
|
score += scoreResult(r, kw, ScoreNormal)
|
}
|
for _, kw := range opts.Expanded {
|
score += scoreResult(r, kw, ScoreExpanded)
|
}
|
for _, kw := range opts.Symptom {
|
score += scoreResult(r, kw, ScoreSymptom)
|
}
|
|
scoreMap[r.ID] = score
|
}
|
|
// 转换为结果列表
|
var results []SearchResult
|
for _, r := range ftsResults {
|
results = append(results, SearchResult{
|
ID: r.ID,
|
Path: r.Path,
|
Title: r.Title,
|
Section: r.Section,
|
Score: scoreMap[r.ID],
|
})
|
}
|
|
// 按分数排序
|
sort.Slice(results, func(i, j int) bool {
|
return results[i].Score > results[j].Score
|
})
|
|
// 限制返回数量
|
if opts.TopN > 0 && len(results) > opts.TopN {
|
results = results[:opts.TopN]
|
}
|
|
return results, nil
|
}
|
|
// scoreResult 计算单个结果的得分
|
func scoreResult(r index.FTSResult, keyword string, scoreType ScoreType) int {
|
score := 0
|
kw := strings.ToLower(keyword)
|
|
// 路径匹配
|
if strings.Contains(strings.ToLower(r.Path), kw) {
|
score += CalcScore(keyword, "path", scoreType)
|
}
|
|
// 标题匹配
|
if strings.Contains(strings.ToLower(r.Title), kw) {
|
score += CalcScore(keyword, "title", scoreType)
|
}
|
|
// 板块匹配
|
if strings.Contains(strings.ToLower(r.Section), kw) {
|
score += CalcScore(keyword, "tag", scoreType)
|
}
|
|
// 内容匹配(FTS 已经匹配,给基础分)
|
score += CalcScore(keyword, "content", scoreType)
|
|
return score
|
}
|