ai_xiaopei
2026-07-25 beb1530ffd6ef2f505acc2bc8809599fbae30c0a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
}