1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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 // 简化版,后续可扩展
| }
|
|