| | |
| | | import ( |
| | | "fmt" |
| | | "os" |
| | | "strings" |
| | | |
| | | "github.com/aisim/kb-cli/internal/index" |
| | | "github.com/aisim/kb-cli/internal/search" |
| | | "github.com/aisim/kb-cli/internal/llm" |
| | | "github.com/aisim/kb-cli/internal/output" |
| | | "github.com/spf13/cobra" |
| | | ) |
| | |
| | | var searchCmd = &cobra.Command{ |
| | | Use: "search [keywords...]", |
| | | Short: "搜索知识库", |
| | | Long: `# search - 搜索 |
| | | Long: `# search - 搜索 |
| | | kb-cli search <关键词> [--top N] [--expanded <词>] [--symptom <词>] [--with-content] [--with-links] [--json] # 搜索知识库,支持关键词、扩展词、症状词`, |
| | | Args: cobra.MinimumNArgs(1), |
| | | RunE: runSearch, |
| | | Args: cobra.MinimumNArgs(1), |
| | | RunE: runSearch, |
| | | } |
| | | |
| | | func init() { |
| | |
| | | searchCmd.Flags().BoolVar(&jsonOut, "json", false, "JSON 格式输出") |
| | | searchCmd.Flags().BoolVar(&withContent, "with-content", false, "返回完整文件内容") |
| | | searchCmd.Flags().BoolVar(&withLinks, "with-links", false, "显示关联文档链接") |
| | | |
| | | // 参数不足时显示 help |
| | | searchCmd.SetUsageTemplate(searchCmd.Long) |
| | | } |
| | | |
| | | func runSearch(cmd *cobra.Command, args []string) error { |
| | |
| | | } |
| | | defer store.Close() |
| | | |
| | | // 检查是否需要重建索引 |
| | | needsRebuild, commit, err := index.NeedsRebuild(store, vaultPath) |
| | | // pre-flight:快速检查索引是否有变更(只 stat 比对,不读内容) |
| | | dirty, err := index.QuickCheck(store, vaultPath) |
| | | if err != nil { |
| | | return fmt.Errorf("检查索引状态失败: %w", err) |
| | | } |
| | | |
| | | if needsRebuild { |
| | | fmt.Fprintln(os.Stderr, "索引过期,正在重建...") |
| | | if err := rebuildIndex(store, commit); err != nil { |
| | | return fmt.Errorf("重建索引失败: %w", err) |
| | | if dirty { |
| | | fmt.Fprintln(os.Stderr, "索引有变更,正在增量同步...") |
| | | if err := syncIndex(store); err != nil { |
| | | return fmt.Errorf("增量同步失败: %w", err) |
| | | } |
| | | } |
| | | |
| | | // 合并所有关键词(支持空格分隔和引号分隔) |
| | | var allKeywords []string |
| | | for _, arg := range args { |
| | | // 如果参数包含空格,按空格分割 |
| | | parts := strings.Fields(arg) |
| | | allKeywords = append(allKeywords, parts...) |
| | | } |
| | | |
| | | if len(allKeywords) == 0 { |
| | | return fmt.Errorf("请提供至少一个关键词") |
| | | } |
| | | |
| | | // 执行搜索 |
| | | cfg := llm.LoadConfig() |
| | | textWeight := cfg.Search.TextWeight |
| | | if textWeight <= 0 || textWeight > 1 { |
| | | textWeight = 0.5 // 缺省 0.5 |
| | | } |
| | | opts := search.SearchOptions{ |
| | | Expanded: expanded, |
| | | Symptom: symptom, |
| | | TopN: topN, |
| | | WithContent: withContent, |
| | | WithLinks: withLinks, |
| | | TextWeight: textWeight, |
| | | } |
| | | |
| | | results, err := search.Search(store, args, opts) |
| | | results, err := search.Search(store, allKeywords, opts) |
| | | if err != nil { |
| | | return fmt.Errorf("搜索失败: %w", err) |
| | | } |