package main import ( "fmt" "math" "math/rand/v2" ops "fn-registry/fn_operations" ) // searchEntitiesFTS searches entities using FTS and returns matching entities. func searchEntitiesFTS(db *ops.DB, query string) ([]ops.Entity, error) { if query == "" { return db.ListEntities("", "") } return db.SearchEntities(query, "") } // searchGraph returns a GraphData subgraph containing matching entities and their direct relations. func searchGraph(db *ops.DB, query string) (GraphData, error) { matches, err := searchEntitiesFTS(db, query) if err != nil { return GraphData{}, fmt.Errorf("searching entities: %w", err) } if len(matches) == 0 { return GraphData{}, nil } matchIDs := map[string]bool{} for _, e := range matches { matchIDs[e.ID] = true } // Get all relations and filter to those touching matched entities allRelations, err := db.ListRelations("") if err != nil { return GraphData{}, err } // Collect neighbor IDs neighborIDs := map[string]bool{} var subRels []ops.Relation for _, r := range allRelations { if matchIDs[r.FromEntity] || matchIDs[r.ToEntity] { subRels = append(subRels, r) neighborIDs[r.FromEntity] = true neighborIDs[r.ToEntity] = true } } // Merge match IDs and neighbor IDs for id := range matchIDs { neighborIDs[id] = true } // Get all entities in the subgraph allEntities, err := db.ListEntities("", "") if err != nil { return GraphData{}, err } degree := map[string]int{} for _, r := range subRels { degree[r.FromEntity]++ degree[r.ToEntity]++ } nodes := make([]GraphNode, 0) for _, e := range allEntities { if !neighborIDs[e.ID] { continue } color, ok := entityTypeColors[e.TypeRef] if !ok { color = "#95a5a6" } size := 8.0 + math.Min(float64(degree[e.ID])*2.0, 20.0) if rs, ok := e.Metadata["risk_score"]; ok { if v, ok := toFloat64(rs); ok { size += v * 0.1 } } nodes = append(nodes, GraphNode{ ID: e.ID, Label: e.Name, Type: e.TypeRef, Color: color, Size: size, X: rand.Float64()*100 - 50, Y: rand.Float64()*100 - 50, Extra: e.Metadata, }) } edges := make([]GraphEdge, 0) for _, r := range subRels { w := 1.0 if r.Weight != nil { w = *r.Weight } edges = append(edges, GraphEdge{ ID: r.ID, Source: r.FromEntity, Target: r.ToEntity, Label: r.Name, Color: "#ffffff30", Size: math.Max(w*3, 0.5), Type: "arrow", }) } return GraphData{Nodes: nodes, Edges: edges}, nil }