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"`
|
}
|