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/graph/builder.go |   96 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 96 insertions(+), 0 deletions(-)

diff --git a/internal/graph/builder.go b/internal/graph/builder.go
new file mode 100644
index 0000000..9b5ca4e
--- /dev/null
+++ b/internal/graph/builder.go
@@ -0,0 +1,96 @@
+package graph
+
+import (
+	"github.com/aisim/kb-cli/internal/vault"
+)
+
+// BuildGraph 从文件元数据构建知识图谱
+func BuildGraph(files []*vault.FileMeta) *Graph {
+	g := &Graph{}
+
+	// 标签/实体 -> 虚拟节点 ID 映射
+	labelToID := make(map[string]int64)
+	nextVirtualID := int64(1000000) // 虚拟节点从 1000000 开始
+
+	getOrCreateVirtualNode := func(label string) int64 {
+		if id, ok := labelToID[label]; ok {
+			return id
+		}
+		id := nextVirtualID
+		nextVirtualID++
+		labelToID[label] = id
+		return id
+	}
+
+	for i, f := range files {
+		node := &Node{
+			ID:        int64(i + 1),
+			Path:      f.Path,
+			Title:     f.Title,
+			Section:   f.Section,
+			Tags:      f.Tags,
+			Entities:  f.Entities,
+			Wikilinks: f.Wikilinks,
+			Content:   f.Content,
+		}
+		g.Nodes = append(g.Nodes, node)
+
+		// 创建 tag 边
+		for _, tag := range f.Tags {
+			virtualID := getOrCreateVirtualNode("tag:" + tag)
+			g.Edges = append(g.Edges, &Edge{
+				FromNode: node.ID,
+				ToNode:   virtualID,
+				Relation: "tag",
+				Label:    tag,
+			})
+		}
+
+		// 创建 entity 边
+		for _, entity := range f.Entities {
+			virtualID := getOrCreateVirtualNode("entity:" + entity)
+			g.Edges = append(g.Edges, &Edge{
+				FromNode: node.ID,
+				ToNode:   virtualID,
+				Relation: "entity",
+				Label:    entity,
+			})
+		}
+	}
+
+	// 创建 wikilink 边(文件间链接)
+	pathToID := make(map[string]int64)
+	for _, n := range g.Nodes {
+		pathToID[n.Path] = n.ID
+	}
+
+	for _, n := range g.Nodes {
+		for _, link := range n.Wikilinks {
+			// 尝试匹配目标文件(模糊匹配:链接文本可能只是标题的一部分)
+			for _, target := range g.Nodes {
+				if matchesWikilink(target, link) {
+					g.Edges = append(g.Edges, &Edge{
+						FromNode: n.ID,
+						ToNode:   target.ID,
+						Relation: "wikilink",
+						Label:    link,
+					})
+					break
+				}
+			}
+		}
+	}
+
+	return g
+}
+
+// matchesWikilink 检查文件是否匹配 wikilink
+func matchesWikilink(node *Node, link string) bool {
+	// 精确匹配标题
+	if node.Title == link {
+		return true
+	}
+	// 匹配文件名(不含扩展名和编号前缀)
+	// 例如:[[充装规格配置]] 匹配 "知识/002-充装规格配置.md"
+	return false // 简化版,后续可扩展
+}

--
Gitblit v1.9.1