document/graph.go

108 lines
2.7 KiB
Go

package document
import (
"fmt"
"strings"
"apigo.cc/go/cast"
"apigo.cc/go/file"
)
// Node 代表文档中的一个节点,可以是一个场景、一个角色或一个知识点。
type Node struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Type string `json:"type,omitempty"`
Meta map[string]any `json:"meta,omitempty"`
Links []string `json:"links,omitempty"` // 与其他节点的关联 (ID 列表)
Parents []string `json:"parents,omitempty"` // 父节点 (用于层级结构)
}
// Graph 是一种具有关联关系的文档,适用于小说大纲、策划分镜、思维导图等场景。
type Graph struct {
filename string
Title string `json:"title"`
Nodes map[string]*Node `json:"nodes"`
}
// NewGraph 创建一个新的关系型文档。
func NewGraph() *Graph {
return &Graph{
Nodes: make(map[string]*Node),
}
}
// AddNode 添加或更新一个节点。
func (g *Graph) AddNode(n *Node) {
if n.ID == "" {
n.ID = cast.To[string](len(g.Nodes) + 1)
}
g.Nodes[n.ID] = n
}
// ToJSON 返回文档的结构化 JSON 表示。
func (g *Graph) ToJSON() string {
res, _ := cast.ToJSON(g)
return res
}
// ToMarkdown 将关系型文档转换为带有 Mermaid 图表的 Markdown。
func (g *Graph) ToMarkdown() string {
var sb strings.Builder
sb.WriteString("# " + g.Title + "\n\n")
// 生成 Mermaid 关系图
sb.WriteString("```mermaid\ngraph TD\n")
for id, node := range g.Nodes {
label := node.Title
if label == "" {
label = id
}
// 节点样式根据类型变化
sb.WriteString(fmt.Sprintf(" %s[\"%s\"]\n", id, label))
for _, link := range node.Links {
sb.WriteString(fmt.Sprintf(" %s --> %s\n", id, link))
}
for _, parent := range node.Parents {
sb.WriteString(fmt.Sprintf(" %s --- %s\n", parent, id))
}
}
sb.WriteString("```\n\n")
// 生成详细内容
for _, node := range g.Nodes {
sb.WriteString("## " + node.Title + " (" + node.ID + ")\n")
if node.Type != "" {
sb.WriteString("> Type: " + node.Type + "\n\n")
}
sb.WriteString(node.Content + "\n\n")
}
return sb.String()
}
// Save 将文档保存为 JSON 文件。
func (g *Graph) Save(filename ...string) error {
path := g.filename
if len(filename) > 0 && filename[0] != "" {
path = filename[0]
}
if path == "" {
return fmt.Errorf("no filename specified")
}
return file.Write(path, g.ToJSON())
}
// OpenGraph 从 JSON 文件加载关系型文档。如果文件不存在,则返回一个新的空白文档。
func OpenGraph(filename string) (*Graph, error) {
g := NewGraph()
g.filename = filename
if file.Exists(filename) {
if err := file.UnmarshalFile(filename, g); err != nil {
return nil, err
}
}
return g, nil
}