handleGraphCommand renders the knowledge graph (or a subject's local graph).
(ctx context.Context, input string)
| 35 | |
| 36 | // handleGraphCommand renders the knowledge graph (or a subject's local graph). |
| 37 | func (cli *ChatCLI) handleGraphCommand(ctx context.Context, input string) { |
| 38 | arg := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(input), "/graph")) |
| 39 | |
| 40 | g := cli.buildKnowledgeGraph() |
| 41 | if g.Len() == 0 { |
| 42 | fmt.Println(colorize(" "+i18n.T("graph.cmd.empty"), ColorYellow)) |
| 43 | return |
| 44 | } |
| 45 | |
| 46 | var include map[string]bool |
| 47 | var title string |
| 48 | switch { |
| 49 | case arg == "" || arg == "full" || arg == "all": |
| 50 | include = selectFullGraphNodes(g) |
| 51 | title = "knowledge graph" |
| 52 | default: |
| 53 | seed, ok := g.Node(arg) |
| 54 | if !ok { |
| 55 | if hits := g.Search(strings.Fields(arg), 1); len(hits) > 0 { |
| 56 | seed = hits[0] |
| 57 | } |
| 58 | } |
| 59 | if seed == nil { |
| 60 | fmt.Println(colorize(" "+i18n.T("graph.cmd.no_node", arg), ColorYellow)) |
| 61 | return |
| 62 | } |
| 63 | include = map[string]bool{seed.ID: true} |
| 64 | for _, n := range g.Neighborhood(seed.ID, graphVizHoodHops, graphVizHoodLimit) { |
| 65 | if n.Kind == knowledge.KindTag { |
| 66 | continue |
| 67 | } |
| 68 | include[n.ID] = true |
| 69 | } |
| 70 | title = "graph: " + seed.Title |
| 71 | } |
| 72 | |
| 73 | dot := graphToDOT(g, include, title) |
| 74 | summary, err := plugins.RenderDOTToFile(ctx, dot, "png", graphVizEngine, "", graphVizDPI) |
| 75 | if err != nil { |
| 76 | fmt.Println(colorize(" "+i18n.T("graph.cmd.error", err.Error()), ColorRed)) |
| 77 | return |
| 78 | } |
| 79 | fmt.Println(colorize(" "+i18n.T("graph.cmd.rendered"), ColorGreen)) |
| 80 | fmt.Println(colorize(" "+summary, ColorGray)) |
| 81 | } |
| 82 | |
| 83 | // selectFullGraphNodes returns the structural nodes to draw: tags are excluded |
| 84 | // (they are keyword glue that turns the view into a hairball). When the graph is |