package cmd import ( "encoding/json" "fmt" "github.com/aisim/kb-cli/internal/graph" "github.com/aisim/kb-cli/internal/index" "github.com/spf13/cobra" ) var graphCmd = &cobra.Command{ Use: "graph", Short: "知识图谱查询与管理", } var graphStatsCmd = &cobra.Command{ Use: "stats", Short: "查看知识图谱统计信息", Long: `kb-cli graph stats [--json] # 查看知识图谱统计信息(节点数、边数、关系类型分布)`, RunE: runGraphStats, } var graphQueryCmd = &cobra.Command{ Use: "query <关键词>", Short: "查询节点的关联关系", Long: `kb-cli graph query <关键词> [--relation <类型>] [--depth <深度>] [--json] # 查询节点的关联关系`, Args: cobra.ExactArgs(1), RunE: runGraphQuery, } var graphRelatedCmd = &cobra.Command{ Use: "related <关键词>", Short: "查找与关键词相关的所有节点", Long: `kb-cli graph related <关键词> [--top N] [--json] # 查找与关键词相关的所有节点`, Args: cobra.ExactArgs(1), RunE: runGraphRelated, } var ( graphRelation string graphDepth int graphJSON bool graphTopN int ) func init() { rootCmd.AddCommand(graphCmd) graphCmd.AddCommand(graphStatsCmd) graphCmd.AddCommand(graphQueryCmd) graphCmd.AddCommand(graphRelatedCmd) // graph stats flags graphStatsCmd.Flags().BoolVar(&graphJSON, "json", false, "JSON 格式输出") // graph query flags graphQueryCmd.Flags().StringVar(&graphRelation, "relation", "", "关系类型(tag/entity/wikilink)") graphQueryCmd.Flags().IntVar(&graphDepth, "depth", 1, "查询深度(1-3)") graphQueryCmd.Flags().BoolVar(&graphJSON, "json", false, "JSON 格式输出") // graph related flags graphRelatedCmd.Flags().IntVar(&graphTopN, "top", 10, "返回前 N 条结果") graphRelatedCmd.Flags().BoolVar(&graphJSON, "json", false, "JSON 格式输出") } func runGraphStats(cmd *cobra.Command, args []string) error { // 打开索引 store, err := index.Open(dbPath) if err != nil { return fmt.Errorf("打开索引失败: %w", err) } defer store.Close() // 获取节点数和边数 nodeCount, err := store.NodeCount() if err != nil { return fmt.Errorf("获取节点数失败: %w", err) } edgeCount, err := store.EdgeCount() if err != nil { return fmt.Errorf("获取边数失败: %w", err) } // 获取关系类型分布 relationStats, err := store.GetRelationStats() if err != nil { return fmt.Errorf("获取关系统计失败: %w", err) } if graphJSON { stats := map[string]interface{}{ "node_count": nodeCount, "edge_count": edgeCount, "relations": relationStats, } data, err := json.MarshalIndent(stats, "", " ") if err != nil { return err } fmt.Println(string(data)) } else { fmt.Printf("知识图谱统计:\n") fmt.Printf(" 节点数: %d\n", nodeCount) fmt.Printf(" 边数: %d\n", edgeCount) fmt.Printf("\n关系类型分布:\n") for rel, count := range relationStats { fmt.Printf(" %-12s %d\n", rel, count) } } return nil } func runGraphQuery(cmd *cobra.Command, args []string) error { keyword := args[0] // 打开索引 store, err := index.Open(dbPath) if err != nil { return fmt.Errorf("打开索引失败: %w", err) } defer store.Close() // 查找匹配关键词的节点 nodes, err := store.FindNodesByKeyword(keyword) if err != nil { return fmt.Errorf("查找节点失败: %w", err) } if len(nodes) == 0 { fmt.Println("未找到匹配的节点") return nil } // 查询每个节点的关联关系 var results []QueryResult for _, node := range nodes { edges, err := store.GetNodeEdges(node.ID, graphRelation, graphDepth) if err != nil { return fmt.Errorf("查询关联失败: %w", err) } results = append(results, QueryResult{ Node: node, Edges: edges, }) } if graphJSON { data, err := json.MarshalIndent(results, "", " ") if err != nil { return err } fmt.Println(string(data)) } else { for _, r := range results { fmt.Printf("\n节点: %s (%s)\n", r.Node.Title, r.Node.Path) if len(r.Edges) == 0 { fmt.Println(" 无关联关系") continue } // 按关系类型分组 grouped := make(map[string][]string) for _, edge := range r.Edges { grouped[edge.Relation] = append(grouped[edge.Relation], edge.Label) } for rel, labels := range grouped { fmt.Printf(" [%s]\n", rel) for _, label := range labels { fmt.Printf(" - %s\n", label) } } } } return nil } func runGraphRelated(cmd *cobra.Command, args []string) error { keyword := args[0] // 打开索引 store, err := index.Open(dbPath) if err != nil { return fmt.Errorf("打开索引失败: %w", err) } defer store.Close() // 查找相关节点 related, err := store.FindRelatedNodes(keyword, graphTopN) if err != nil { return fmt.Errorf("查找相关节点失败: %w", err) } if len(related) == 0 { fmt.Println("未找到相关节点") return nil } if graphJSON { data, err := json.MarshalIndent(related, "", " ") if err != nil { return err } fmt.Println(string(data)) } else { fmt.Printf("与 '%s' 相关的节点:\n\n", keyword) for i, r := range related { fmt.Printf("%d. %s\n", i+1, r.Title) fmt.Printf(" 路径: %s\n", r.Path) fmt.Printf(" 板块: %s\n", r.Section) fmt.Printf(" 关联度: %d\n", r.Relevance) if len(r.Tags) > 0 { fmt.Printf(" 标签: %v\n", r.Tags) } fmt.Println() } } return nil } // QueryResult 查询结果 type QueryResult struct { Node *graph.Node `json:"node"` Edges []*graph.Edge `json:"edges"` } // RelatedNode 相关节点 type RelatedNode struct { ID int64 `json:"id"` Path string `json:"path"` Title string `json:"title"` Section string `json:"section"` Tags []string `json:"tags"` Relevance int `json:"relevance"` }