Skip to content

Commit 4d01e67

Browse files
committed
feat: add agent connections graph component to agent page and improve force graph responsiveness
1 parent 10f77a2 commit 4d01e67

File tree

4 files changed

+118
-7
lines changed

4 files changed

+118
-7
lines changed

apps/torus-portal/src/app/(pages)/portal/_components/force-graph-2d/force-graph-2d.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ interface ForceGraph2DProps {
4141
onSelectionChange?: (nodeId: string | null) => void;
4242
selectedSwarmId?: string | null;
4343
swarmCenterNodeId?: string | null;
44+
/** When true, the graph fits within its parent container instead of being full-screen */
45+
contained?: boolean;
4446
}
4547

4648
export function ForceGraphCanvas2D(props: ForceGraph2DProps) {
@@ -166,7 +168,7 @@ export function ForceGraphCanvas2D(props: ForceGraph2DProps) {
166168
height,
167169
resolution: 1,
168170
antialias: true,
169-
resizeTo: window,
171+
resizeTo: props.contained ? container : window,
170172
backgroundColor: 0x111111,
171173
});
172174

@@ -487,7 +489,11 @@ export function ForceGraphCanvas2D(props: ForceGraph2DProps) {
487489
return (
488490
<div
489491
ref={containerRef}
490-
className="bg-background animate-fade animate-delay-1000 fixed inset-0 z-0"
492+
className={
493+
props.contained
494+
? "bg-background h-full w-full"
495+
: "bg-background animate-fade animate-delay-1000 fixed inset-0 z-0"
496+
}
491497
/>
492498
);
493499
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"use client";
2+
3+
import { Loading } from "@torus-ts/ui/components/loading";
4+
import { ForceGraphCanvas2D } from "~/app/(pages)/portal/_components/force-graph-2d/force-graph-2d";
5+
import { getConnectedNodesSwarm } from "~/app/(pages)/portal/_components/force-graph-2d/force-graph-2d-utils";
6+
import { useGraphData } from "~/app/(pages)/portal/_components/force-graph/use-graph-data";
7+
import { useMemo } from "react";
8+
9+
interface AgentConnectionsGraphProps {
10+
agentKey: string;
11+
}
12+
13+
export function AgentConnectionsGraph({
14+
agentKey,
15+
}: AgentConnectionsGraphProps) {
16+
const { graphData, isLoading, allocatorAddress } = useGraphData();
17+
18+
// Filter graph data to show only nodes connected to this agent
19+
const filteredGraphData = useMemo(() => {
20+
if (!graphData) return null;
21+
22+
// Find the agent node
23+
const agentNode = graphData.nodes.find((n) => n.id === agentKey);
24+
if (!agentNode) return null;
25+
26+
// Get all connected nodes using the swarm traversal logic
27+
const connectedNodeIds = getConnectedNodesSwarm(
28+
agentKey,
29+
graphData.nodes,
30+
graphData.links,
31+
allocatorAddress,
32+
);
33+
34+
// Filter nodes (exclude allocator from the filtered view)
35+
const filteredNodes = graphData.nodes.filter(
36+
(node) => connectedNodeIds.has(node.id) && node.id !== allocatorAddress,
37+
);
38+
39+
// Filter links to only include those between connected nodes (excluding allocator)
40+
const filteredLinks = graphData.links.filter((link) => {
41+
const sourceId =
42+
typeof link.source === "string"
43+
? link.source
44+
: (link.source as { id: string }).id;
45+
const targetId =
46+
typeof link.target === "string"
47+
? link.target
48+
: (link.target as { id: string }).id;
49+
return (
50+
connectedNodeIds.has(sourceId) &&
51+
connectedNodeIds.has(targetId) &&
52+
sourceId !== allocatorAddress &&
53+
targetId !== allocatorAddress
54+
);
55+
});
56+
57+
// Only show graph if there are connections (more than just the agent itself)
58+
if (filteredNodes.length <= 1) return null;
59+
60+
return {
61+
nodes: filteredNodes,
62+
links: filteredLinks,
63+
};
64+
}, [graphData, agentKey, allocatorAddress]);
65+
66+
if (isLoading) {
67+
return (
68+
<div className="flex h-[400px] items-center justify-center rounded-lg border border-zinc-800 bg-zinc-900/50">
69+
<div className="flex items-center gap-2 text-sm text-zinc-400">
70+
<Loading /> Loading network connections...
71+
</div>
72+
</div>
73+
);
74+
}
75+
76+
if (!filteredGraphData) {
77+
return (
78+
<div className="flex h-[200px] items-center justify-center rounded-lg border border-zinc-800 bg-zinc-900/50">
79+
<p className="text-sm text-zinc-400">No network connections found</p>
80+
</div>
81+
);
82+
}
83+
84+
return (
85+
<div className="relative h-[400px] overflow-hidden rounded-lg border border-zinc-800">
86+
<ForceGraphCanvas2D
87+
graphData={filteredGraphData}
88+
onNodeClick={() => {
89+
/* empty */
90+
}}
91+
allocatorAddress={allocatorAddress}
92+
swarmCenterNodeId={agentKey}
93+
contained
94+
/>
95+
</div>
96+
);
97+
}

apps/torus-portal/src/app/(pages)/root-allocator/(expanded-pages)/agent/[slug]/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Link from "next/link";
1616
import { notFound } from "next/navigation";
1717
import { Suspense } from "react";
1818
import { PenaltyList } from "../../../_components/penalties-list";
19+
import { AgentConnectionsGraph } from "./components/agent-connections-graph";
1920
import { AgentInfoCard } from "./components/agent-info-card";
2021
import { ExpandedViewSocials } from "./components/expanded-view-socials";
2122

@@ -160,6 +161,11 @@ export default async function AgentPage({ params }: Readonly<AgentPageProps>) {
160161
</Card>
161162

162163
<MarkdownView source={metadata.description} />
164+
165+
<div className="mt-6">
166+
<h3 className="mb-4 text-lg font-semibold">Sub-Agents Graph</h3>
167+
<AgentConnectionsGraph agentKey={agentKey} />
168+
</div>
163169
</div>
164170

165171
<div className="flex flex-col gap-6 md:w-1/3">

packages/ui/src/components/agent-card/agent-card-header.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,13 @@ export function AgentCardHeader({
8888
<div className="flex w-full items-center justify-between gap-1 md:absolute md:inset-x-0 md:top-0">
8989
<div className="flex items-center gap-1">
9090
<AgentCardSocialsInfo socials={socialsList} />
91-
{isWhitelisted && subagentCount !== undefined && subagentCount > 0 && (
92-
<Badge variant="secondary" className="whitespace-nowrap">
93-
{subagentCount} Subagent{subagentCount !== 1 ? "s" : ""}
94-
</Badge>
95-
)}
91+
{isWhitelisted &&
92+
subagentCount !== undefined &&
93+
subagentCount > 0 && (
94+
<Badge variant="secondary" className="whitespace-nowrap">
95+
{subagentCount} Subagent{subagentCount !== 1 ? "s" : ""}
96+
</Badge>
97+
)}
9698
</div>
9799
<AgentBadge
98100
isAgentSelected={isAgentSelected}

0 commit comments

Comments
 (0)