Relationship Explorer
How things connect, as an interactive graph you can pan, zoom and select from. Hierarchies, dependencies, cause and effect, networks.
≈ 1,944 tokens per use (tool 914 · guidance 326 · typical payload 704)How token costs are estimated
Example: Relationship Explorer
Show the org chart under Dana.
Engineering under Dana Whitfield
Engineering under Dana Whitfield: 18 nodes and 17 links, drawn as a tree.
- Reports to
When to use
- The answer is how things connect: an org chart, what depends on a service, what caused a metric to move, how companies or people are linked.
- There are more links than a sentence or a list can carry, roughly 5 to 60 nodes.
When not to use
- Things happen in order over time. Use a Timeline.
- It's a plain list or a ranking with no links between items. Use a list or a Data Table.
- There are only two or three nodes. Say it in a sentence.
- The links are guesses. Only draw relationships that come from data.
How the agent should use it
- Take nodes, links and
healthfrom tool results. Never invent a link. - Pick the layout for the shape of the data:
treewhen each node has one parent (org charts, folders),flowwhen links have a direction (dependencies, causes, pipelines),networkfor undirected connections (up to 40 nodes). - Use edge kinds so arrows read right:
depends-onpoints at the dependency,reports-topoints at the manager,causespoints at the effect,blockspoints at what's blocked,relatedhas no arrow. - Set
focusIdto the node the user asked about; it starts selected, with its neighbours highlighted. SetrootIdto the top of a tree. - Label cause-and-effect links with the evidence, such as "+38% errors". Keep labels short; long ones are cut on the canvas and shown in full on hover.
- Only set
selectablewhen the user's choice changes what you do next. Onexplain, explain that node and its links. - Lifecycle: for live data such as service health, set
fetchedAt(andstaleAfterMs) on the call so the Renderer marks it stale. Use supersede when you redraw the same graph with fresh data.expiresAtisn't needed.
Layouts
| Layout | Algorithm | Notes |
|---|---|---|
tree | Tidy tree (Reingold–Tilford, simplified): subtrees are packed so their outlines clear each other, and parents sit centred over their children | from is the parent, except for reports-to, where to is. Three levels show; deeper subtrees start folded with a "+N" count, and focusing a deep node opens its branch. Several roots sit side by side; extra links that aren't part of the tree are drawn straight |
flow | Layered (Sugiyama-lite): longest-path layers from the sources, four barycenter sweeps to reduce crossings, curved edges through one waypoint per skipped layer | A link that closes a loop is drawn dashed, running backwards, with a "Loop, drawn backwards" legend entry. rootId decides where reading starts, and so which link in a loop runs backwards |
network | Small force layout: a seeded start, a fixed number of iterations, then rotated to the card's long axis and nudged so no two nodes overlap | The same data always lands in the same place. Over 40 nodes it's drawn as a flow, with a note |
Every layout is deterministic and lives in relationship-explorer-utils.ts, with no dependencies. direction sets which way trees and flows grow. Leave it out and they grow right, except that a flow in a card narrower than 560px is laid out both ways and keeps whichever is shown larger. Setting direction turns that off.
Reading the graph
- Nodes show the label, the
sublabelandkind, andhealthas an icon with a word ("Critical"). Text that doesn't fit is cut with an ellipsis; hovering shows it in full. - Edges are styled by kind, so they read without color:
depends-onand plain links are solid arrows,causesis a thicker arrow,blocksis dashed,reports-tois a line without an arrow, andrelatedis dotted.strength(0–1) makes a link thicker. The legend is one compact row under the graph and lists only the kinds and health states in the data. The only other text under the graph is a short data note when something wasn't drawn (over the node limit, unknown ids). - Selecting a node (click, tap or keyboard) keeps it and its neighbours at full strength and dims the rest. The panel under the graph shows its detail, its links grouped by meaning ("Depends on", "Used by", "Caused by"…) as buttons that move the selection, and an Open link when
urlis http(s). - Pan by dragging. Zoom with the buttons, Ctrl or ⌘ + scroll, or a pinch. A plain scroll wheel keeps scrolling the page. Fit zooms out to the whole graph.
- The first view stays readable: the whole graph shows if it fits at 75% zoom or more. If it doesn't, the view starts at 75%, centred on
focusId(or the root, or the first node), and you pan or press Fit for the overview. The canvas is as tall as the content at that zoom, between 280 and 520px. The view follows the stream and opened subtrees until you pan or zoom. - Problems are shown, not dropped: links to ids that aren't in
nodesare listed under the graph, and so are nodes past the limit of 60.
Accessibility
The canvas is hidden from assistive tech. A visually hidden structure mirrors it and is the keyboard path:
- Tree layout: a
role="tree"with tree items (level, position and expanded state). Up and Down move, Right opens a branch or moves into it, Left closes it or moves to the parent, Home and End jump, Enter toggles a branch. - Flow and network: a
role="listbox"; each option reads the node and its links, such as "Auth service, Identity team, service, Critical. Depends on Notifications, Users DB, Token cache and 1 more. Used by Admin console, API gateway, Orders and 3 more."
Selection follows focus, so moving through the list highlights the node on the canvas, draws a focus ring around it, and pans it into view. A short key hint shows over the canvas while the list has focus. Health is always an icon plus a word, never color alone, and transitions follow prefers-reduced-motion.
Installation
npx shadcn@latest add https://your-site.com/r/relationship-explorer.json
Usage
import { tool } from "ai"
import { relationshipExplorerTool } from "@/components/genui/relationship-explorer.schema"
const tools = {
[relationshipExplorerTool.name]: tool({
description: relationshipExplorerTool.description,
inputSchema: relationshipExplorerTool.inputSchema,
}),
}Without selectable the graph is the whole answer, so give the tool an execute that returns immediately. With it, the detail panel adds Choose and Explain this, and the agent receives one of:
{ action: "select", nodeId: "auth" }
{ action: "explain", nodeId: "auth" }With LangGraph
Push it from a graph node with ui.push({ name: "show_relationships", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
Pass the stored tool output as result. The explorer selects that node, shows what was sent, and never calls onResult again. Panning, zooming and browsing still work:
<RelationshipExplorer
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
title | string? | |
layout | "tree" | "flow" | "network" | |
nodes | { id, label, sublabel?, kind?, health?, detail?, url? }[] | 1–60. Data-bound. kind: free text, up to 24 characters. health: ok, warning, critical, unknown. url: http(s) only |
edges | { from, to, label?, kind?, strength? }[] | Up to 200. Data-bound. kind: depends-on, reports-to, causes, related, blocks; omit for a plain arrow. strength 0–1 |
rootId | string? | Tree: the top node. Flow: where reading starts |
focusId | string? | Selected at first |
direction | "down" | "right" | Trees and flows. Omit it to grow right, with narrow cards choosing for flows |
selectable | boolean | Adds Choose and Explain this. Default false |
Host props: status (streaming, ready, submitted), onResult, result and className.