From 09c2a03cefa687832257d3b816da50db3cfcc0f2 Mon Sep 17 00:00:00 2001
From: ai_xiaopei <xiaopei@aisim.cn>
Date: Sat, 25 Jul 2026 23:48:11 +0800
Subject: [PATCH] feat: 实现输出格式化

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

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

--
Gitblit v1.9.1