From 867e1e9eb133198af79fb2b94c9522a46e85e83d Mon Sep 17 00:00:00 2001 From: Vectry Date: Mon, 9 Feb 2026 23:58:41 +0000 Subject: [PATCH] feat: decision tree visualization with React Flow + Dagre auto-layout --- apps/web/package.json | 7 +- apps/web/src/app/api/traces/[id]/route.ts | 39 +- .../src/app/dashboard/traces/[id]/page.tsx | 50 +- apps/web/src/components/decision-tree.tsx | 797 ++++++++++++++++++ apps/web/src/components/trace-detail.tsx | 24 +- package-lock.json | 258 +++++- 6 files changed, 1151 insertions(+), 24 deletions(-) create mode 100644 apps/web/src/components/decision-tree.tsx diff --git a/apps/web/package.json b/apps/web/package.json index c37cce7..f0dd43f 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -13,13 +13,16 @@ }, "dependencies": { "@agentlens/database": "*", + "@dagrejs/dagre": "^2.0.4", + "@xyflow/react": "^12.10.0", + "lucide-react": "^0.469.0", "next": "^15.1.0", "react": "^19.0.0", - "react-dom": "^19.0.0", - "lucide-react": "^0.469.0" + "react-dom": "^19.0.0" }, "devDependencies": { "@tailwindcss/postcss": "^4.0.0", + "@types/dagre": "^0.7.53", "@types/node": "^22.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", diff --git a/apps/web/src/app/api/traces/[id]/route.ts b/apps/web/src/app/api/traces/[id]/route.ts index 5cef035..e1d2910 100644 --- a/apps/web/src/app/api/traces/[id]/route.ts +++ b/apps/web/src/app/api/traces/[id]/route.ts @@ -38,7 +38,44 @@ export async function GET( return NextResponse.json({ error: "Trace not found" }, { status: 404 }); } - return NextResponse.json({ trace }, { status: 200 }); + // Transform data to match frontend expectations + const transformedTrace = { + ...trace, + decisionPoints: trace.decisionPoints.map((dp) => ({ + id: dp.id, + type: dp.type, + chosenAction: typeof dp.chosen === "string" ? dp.chosen : JSON.stringify(dp.chosen), + alternatives: dp.alternatives.map((alt) => (typeof alt === "string" ? alt : JSON.stringify(alt))), + reasoning: dp.reasoning, + contextSnapshot: dp.contextSnapshot as Record | null, + confidence: null, // Not in schema, default to null + timestamp: dp.timestamp.toISOString(), + parentSpanId: dp.parentSpanId, + })), + spans: trace.spans.map((span) => ({ + id: span.id, + name: span.name, + type: span.type, + status: span.status === "COMPLETED" ? "OK" : span.status === "ERROR" ? "ERROR" : "CANCELLED", + startedAt: span.startedAt.toISOString(), + endedAt: span.endedAt?.toISOString() ?? null, + durationMs: span.durationMs, + input: span.input, + output: span.output, + metadata: (span.metadata as Record) ?? {}, + parentSpanId: span.parentSpanId, + })), + events: trace.events.map((event) => ({ + id: event.id, + type: event.type, + name: event.name, + timestamp: event.timestamp.toISOString(), + metadata: (event.metadata as Record) ?? {}, + spanId: event.spanId, + })), + }; + + return NextResponse.json({ trace: transformedTrace }, { status: 200 }); } catch (error) { console.error("Error retrieving trace:", error); return NextResponse.json({ error: "Internal server error" }, { status: 500 }); diff --git a/apps/web/src/app/dashboard/traces/[id]/page.tsx b/apps/web/src/app/dashboard/traces/[id]/page.tsx index d26e8fc..0e8d064 100644 --- a/apps/web/src/app/dashboard/traces/[id]/page.tsx +++ b/apps/web/src/app/dashboard/traces/[id]/page.tsx @@ -1,18 +1,17 @@ import { notFound } from "next/navigation"; import { TraceDetail } from "@/components/trace-detail"; -interface TraceResponse { - trace: { - id: string; - name: string; - status: "RUNNING" | "COMPLETED" | "ERROR"; - startedAt: string; - endedAt: string | null; - durationMs: number | null; - tags: string[]; - metadata: Record; - costUsd: number | null; - }; +interface TraceData { + id: string; + name: string; + status: "RUNNING" | "COMPLETED" | "ERROR"; + startedAt: string; + endedAt: string | null; + durationMs: number | null; + tags: string[]; + metadata: Record; + costUsd: number | null; + totalCost: number | null; decisionPoints: Array<{ id: string; type: string; @@ -22,6 +21,7 @@ interface TraceResponse { contextSnapshot: Record | null; confidence: number | null; timestamp: string; + parentSpanId: string | null; }>; spans: Array<{ id: string; @@ -34,6 +34,7 @@ interface TraceResponse { input: unknown; output: unknown; metadata: Record; + parentSpanId: string | null; }>; events: Array<{ id: string; @@ -41,9 +42,14 @@ interface TraceResponse { name: string; timestamp: string; metadata: Record; + spanId: string | null; }>; } +interface TraceResponse { + trace: TraceData; +} + async function getTrace(id: string): Promise { try { const res = await fetch(`http://localhost:3000/api/traces/${id}`, { @@ -76,12 +82,24 @@ export default async function TraceDetailPage({ params }: TraceDetailPageProps) notFound(); } + const { trace } = data; + return ( ); } diff --git a/apps/web/src/components/decision-tree.tsx b/apps/web/src/components/decision-tree.tsx new file mode 100644 index 0000000..a84d222 --- /dev/null +++ b/apps/web/src/components/decision-tree.tsx @@ -0,0 +1,797 @@ +"use client"; + +import { useCallback, useMemo, useState } from "react"; +import { + ReactFlow, + useNodesState, + useEdgesState, + addEdge, + type Connection, + type Edge, + type Node, + Handle, + Position, + MiniMap, + Controls, + Background, + BackgroundVariant, + ConnectionLineType, +} from "@xyflow/react"; +import "@xyflow/react/dist/style.css"; +import dagre from "@dagrejs/dagre"; +import { + GitBranch, + Activity, + Zap, + Terminal, + AlertCircle, + CheckCircle, + XCircle, + Clock, + ChevronDown, + ChevronRight, + Lightbulb, + Layers, +} from "lucide-react"; +import { cn, formatDuration } from "@/lib/utils"; + +// Types matching trace-detail.tsx +type TraceStatus = "RUNNING" | "COMPLETED" | "ERROR"; + +interface DecisionPoint { + id: string; + type: string; + chosenAction: string; + alternatives: string[]; + reasoning: string | null; + contextSnapshot: Record | null; + confidence: number | null; + timestamp: string; + parentSpanId?: string | null; +} + +interface Span { + id: string; + name: string; + type: string; + status: "OK" | "ERROR" | "CANCELLED"; + startedAt: string; + endedAt: string | null; + durationMs: number | null; + input: unknown; + output: unknown; + metadata: Record; + parentSpanId?: string | null; +} + +interface Event { + id: string; + type: string; + name: string; + timestamp: string; + metadata: Record; + spanId?: string | null; +} + +interface Trace { + id: string; + name: string; + status: TraceStatus; + startedAt: string; + endedAt: string | null; + durationMs: number | null; + tags: string[]; + metadata: Record; + costUsd: number | null; +} + +interface DecisionTreeProps { + trace: Trace; + decisionPoints: DecisionPoint[]; + spans: Span[]; + events: Event[]; +} + +// Color configurations +const spanTypeColors: Record = { + LLM_CALL: { + bg: "bg-purple-500/10", + text: "text-purple-400", + border: "border-purple-500/20", + }, + TOOL_CALL: { + bg: "bg-blue-500/10", + text: "text-blue-400", + border: "border-blue-500/20", + }, + CHAIN: { + bg: "bg-amber-500/10", + text: "text-amber-400", + border: "border-amber-500/20", + }, + AGENT: { + bg: "bg-cyan-500/10", + text: "text-cyan-400", + border: "border-cyan-500/20", + }, + MEMORY_OP: { + bg: "bg-pink-500/10", + text: "text-pink-400", + border: "border-pink-500/20", + }, + CUSTOM: { + bg: "bg-neutral-700/30", + text: "text-neutral-400", + border: "border-neutral-600/30", + }, +}; + +const spanStatusColors: Record = { + OK: { dot: "bg-emerald-400" }, + ERROR: { dot: "bg-red-400" }, + CANCELLED: { dot: "bg-amber-400", pulse: true }, +}; + +const decisionTypeColors: Record = { + TOOL_SELECTION: { + bg: "bg-blue-500/10", + text: "text-blue-400", + border: "border-blue-500/20", + }, + PATH_SELECTION: { + bg: "bg-purple-500/10", + text: "text-purple-400", + border: "border-purple-500/20", + }, + RESPONSE_FORMULATION: { + bg: "bg-emerald-500/10", + text: "text-emerald-400", + border: "border-emerald-500/20", + }, + DEFAULT: { + bg: "bg-neutral-700/30", + text: "text-neutral-400", + border: "border-neutral-600/30", + }, +}; + +const eventTypeIcons: Record> = { + LLM_CALL: Zap, + TOOL_CALL: Terminal, + ERROR: AlertCircle, + DEFAULT: Activity, +}; + +// Node data types +type TraceNodeData = { + trace: Trace; +} & Record; + +type SpanNodeData = { + span: Span; + maxDuration: number; +} & Record; + +type DecisionNodeData = { + decision: DecisionPoint; +} & Record; + +type EventNodeData = { + event: Event; +} & Record; + +// Custom Node Components +function TraceNode({ data, selected }: { data: TraceNodeData; selected?: boolean }) { + const { trace } = data; + + const statusConfig: Record; color: string }> = { + RUNNING: { icon: Activity, color: "text-amber-400" }, + COMPLETED: { icon: CheckCircle, color: "text-emerald-400" }, + ERROR: { icon: XCircle, color: "text-red-400" }, + }; + + const status = statusConfig[trace.status]; + const StatusIcon = status.icon; + + return ( +
+ +
+
+ +
+
+

{trace.name}

+
+ + {trace.status} +
+
+
+
+ + {formatDuration(trace.durationMs)} +
+ +
+ ); +} + +function SpanNode({ data, selected }: { data: SpanNodeData; selected?: boolean }) { + const { span, maxDuration } = data; + const colors = spanTypeColors[span.type] || spanTypeColors.CUSTOM; + const statusColor = spanStatusColors[span.status] || spanStatusColors.CANCELLED; + const durationPercent = maxDuration > 0 ? ((span.durationMs || 0) / maxDuration) * 100 : 0; + + return ( +
+ +
+
+
+

{span.name}

+
+ {span.type} +
+
+
+
+
+
+
+
+ {formatDuration(span.durationMs)} +
+
+ +
+ ); +} + +function DecisionNode({ data, selected }: { data: DecisionNodeData; selected?: boolean }) { + const { decision } = data; + const [expanded, setExpanded] = useState(false); + const colors = decisionTypeColors[decision.type] || decisionTypeColors.DEFAULT; + + return ( +
+ +
+
+
+ {decision.type} +
+ {decision.confidence !== null && ( + + {Math.round(decision.confidence * 100)}% + + )} +
+

{decision.chosenAction}

+ {decision.alternatives.length > 0 && ( + + )} + {expanded && decision.alternatives.length > 0 && ( +
+ {decision.alternatives.map((alt, idx) => ( +
+ #{idx + 1} + {alt} +
+ ))} +
+ )} + {decision.reasoning && ( +
+
+ + {decision.reasoning} +
+
+ )} +
+ +
+ ); +} + +function EventNode({ data, selected }: { data: EventNodeData; selected?: boolean }) { + const { event } = data; + const Icon = eventTypeIcons[event.type] || eventTypeIcons.DEFAULT; + + return ( +
+ +
+
+ +
+
+

{event.name}

+

{event.type}

+
+
+ +
+ ); +} + +// Node types must be defined outside the component to avoid re-renders +const nodeTypes = { + trace: TraceNode, + span: SpanNode, + decision: DecisionNode, + event: EventNode, +}; + +// Build the tree layout using Dagre +function buildTreeLayout( + trace: Trace, + spans: Span[], + decisionPoints: DecisionPoint[], + events: Event[] +): { nodes: Node[]; edges: Edge[] } { + const g = new dagre.graphlib.Graph(); + g.setDefaultEdgeLabel(() => ({})); + g.setGraph({ + rankdir: "TB", + ranksep: 80, + nodesep: 40, + }); + + const nodes: Node[] = []; + const edges: Edge[] = []; + + // Add root trace node + const traceNodeId = `trace-${trace.id}`; + g.setNode(traceNodeId, { width: 300, height: 100 }); + nodes.push({ + id: traceNodeId, + type: "trace", + data: { trace } as TraceNodeData, + position: { x: 0, y: 0 }, + }); + + // Calculate max duration for span bars + const maxDuration = Math.max(...spans.map((s) => s.durationMs || 0), 1); + + // Add span nodes and build parent-child relationships + const spanMap = new Map(); // spanId -> nodeId + const childSpans = new Map(); // parentSpanId -> spanIds + + spans.forEach((span) => { + const nodeId = `span-${span.id}`; + spanMap.set(span.id, nodeId); + + if (span.parentSpanId) { + const siblings = childSpans.get(span.parentSpanId) || []; + siblings.push(span.id); + childSpans.set(span.parentSpanId, siblings); + } + + g.setNode(nodeId, { width: 260, height: 80 }); + nodes.push({ + id: nodeId, + type: "span", + data: { span, maxDuration } as SpanNodeData, + position: { x: 0, y: 0 }, + }); + }); + + // Connect spans to their parents or to trace root + spans.forEach((span) => { + const nodeId = spanMap.get(span.id)!; + if (span.parentSpanId && spanMap.has(span.parentSpanId)) { + const parentId = spanMap.get(span.parentSpanId)!; + g.setEdge(parentId, nodeId); + edges.push({ + id: `e-${parentId}-${nodeId}`, + source: parentId, + target: nodeId, + type: ConnectionLineType.SmoothStep, + animated: true, + style: { stroke: span.status === "ERROR" ? "#f87171" : "#34d399" }, + }); + } else { + // Top-level span - connect to trace root + g.setEdge(traceNodeId, nodeId); + edges.push({ + id: `e-${traceNodeId}-${nodeId}`, + source: traceNodeId, + target: nodeId, + type: ConnectionLineType.SmoothStep, + animated: true, + style: { stroke: span.status === "ERROR" ? "#f87171" : "#34d399" }, + }); + } + }); + + // Add decision nodes + decisionPoints.forEach((decision) => { + const nodeId = `decision-${decision.id}`; + g.setNode(nodeId, { width: 240, height: 90 }); + nodes.push({ + id: nodeId, + type: "decision", + data: { decision } as DecisionNodeData, + position: { x: 0, y: 0 }, + }); + + // Connect to parent span or trace root + if (decision.parentSpanId && spanMap.has(decision.parentSpanId)) { + const parentId = spanMap.get(decision.parentSpanId)!; + g.setEdge(parentId, nodeId); + edges.push({ + id: `e-${parentId}-${nodeId}`, + source: parentId, + target: nodeId, + type: ConnectionLineType.SmoothStep, + animated: true, + style: { stroke: "#60a5fa" }, + }); + } else { + g.setEdge(traceNodeId, nodeId); + edges.push({ + id: `e-${traceNodeId}-${nodeId}`, + source: traceNodeId, + target: nodeId, + type: ConnectionLineType.SmoothStep, + animated: true, + style: { stroke: "#60a5fa" }, + }); + } + }); + + // Add event nodes + events.forEach((event) => { + const nodeId = `event-${event.id}`; + g.setNode(nodeId, { width: 180, height: 50 }); + nodes.push({ + id: nodeId, + type: "event", + data: { event } as EventNodeData, + position: { x: 0, y: 0 }, + }); + + // Connect to span or trace root + if (event.spanId && spanMap.has(event.spanId)) { + const parentId = spanMap.get(event.spanId)!; + g.setEdge(parentId, nodeId); + edges.push({ + id: `e-${parentId}-${nodeId}`, + source: parentId, + target: nodeId, + type: ConnectionLineType.SmoothStep, + animated: true, + style: { stroke: "#a78bfa" }, + }); + } else { + g.setEdge(traceNodeId, nodeId); + edges.push({ + id: `e-${traceNodeId}-${nodeId}`, + source: traceNodeId, + target: nodeId, + type: ConnectionLineType.SmoothStep, + animated: true, + style: { stroke: "#a78bfa" }, + }); + } + }); + + // Calculate layout + dagre.layout(g); + + // Update node positions from Dagre + nodes.forEach((node) => { + const dagreNode = g.node(node.id); + node.position = { + x: dagreNode.x - dagreNode.width / 2, + y: dagreNode.y - dagreNode.height / 2, + }; + }); + + return { nodes, edges }; +} + +export function DecisionTree({ trace, spans, decisionPoints, events }: DecisionTreeProps) { + const { nodes: initialNodes, edges: initialEdges } = useMemo( + () => buildTreeLayout(trace, spans, decisionPoints, events), + [trace, spans, decisionPoints, events] + ); + + const [nodes, _, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const [selectedNode, setSelectedNode] = useState(null); + + const onConnect = useCallback( + (params: Connection) => { + setEdges((eds) => addEdge(params, eds)); + }, + [setEdges] + ); + + const onNodeClick = useCallback((_: React.MouseEvent, node: Node) => { + setSelectedNode((prev) => (prev?.id === node.id ? null : node)); + }, []); + + // Empty state + if (spans.length === 0 && decisionPoints.length === 0 && events.length === 0) { + return ( +
+ +

No trace data available

+

+ This trace does not contain any spans, decisions, or events to visualize. +

+
+ ); + } + + return ( +
+
+ + + { + switch (node.type) { + case "trace": + return "#10b981"; + case "span": + return "#3b82f6"; + case "decision": + return "#8b5cf6"; + case "event": + return "#f59e0b"; + default: + return "#6b7280"; + } + }} + maskColor="rgba(23, 23, 23, 0.8)" + className="!bg-neutral-900/80 !border-neutral-700" + /> + + +
+ + {/* Details Panel */} + {selectedNode && ( +
+
+

Details

+ +
+ +
+ )} +
+ ); +} + +function NodeDetails({ node }: { node: Node }) { + switch (node.type) { + case "trace": { + const data = node.data as unknown as TraceNodeData; + return ( +
+
+ Name +

{data.trace.name}

+
+
+ Status +

{data.trace.status}

+
+
+ Duration +

{formatDuration(data.trace.durationMs)}

+
+ {data.trace.costUsd !== null && ( +
+ Cost +

${data.trace.costUsd.toFixed(4)}

+
+ )} + {data.trace.tags.length > 0 && ( +
+ Tags +
+ {data.trace.tags.map((tag) => ( + + {tag} + + ))} +
+
+ )} +
+ ); + } + case "span": { + const data = node.data as unknown as SpanNodeData; + return ( +
+
+ Name +

{data.span.name}

+
+
+ Type +

{data.span.type}

+
+
+ Status +

{data.span.status}

+
+
+ Duration +

{formatDuration(data.span.durationMs)}

+
+ {Boolean(data.span.input) && ( +
+ Input +
+                {JSON.stringify(data.span.input, null, 2)}
+              
+
+ )} + {Boolean(data.span.output) && ( +
+ Output +
+                {JSON.stringify(data.span.output, null, 2)}
+              
+
+ )} +
+ ); + } + case "decision": { + const data = node.data as unknown as DecisionNodeData; + return ( +
+
+ Type +

{data.decision.type}

+
+
+ Chosen Action +

{data.decision.chosenAction}

+
+ {data.decision.confidence !== null && ( +
+ Confidence +

{Math.round(data.decision.confidence * 100)}%

+
+ )} + {data.decision.alternatives.length > 0 && ( +
+ + Alternatives ({data.decision.alternatives.length}) + +
+ {data.decision.alternatives.map((alt, idx) => ( +

+ {idx + 1}. {alt} +

+ ))} +
+
+ )} + {data.decision.reasoning && ( +
+ Reasoning +

“{data.decision.reasoning}”

+
+ )} +
+ ); + } + case "event": { + const data = node.data as unknown as EventNodeData; + return ( +
+
+ Name +

{data.event.name}

+
+
+ Type +

{data.event.type}

+
+
+ Timestamp +

{new Date(data.event.timestamp).toLocaleString()}

+
+ {Object.keys(data.event.metadata).length > 0 && ( +
+ Metadata +
+                {JSON.stringify(data.event.metadata, null, 2)}
+              
+
+ )} +
+ ); + } + default: + return null; + } +} diff --git a/apps/web/src/components/trace-detail.tsx b/apps/web/src/components/trace-detail.tsx index 0124261..027a910 100644 --- a/apps/web/src/components/trace-detail.tsx +++ b/apps/web/src/components/trace-detail.tsx @@ -20,9 +20,10 @@ import { Terminal, } from "lucide-react"; import { cn, formatDuration, formatRelativeTime } from "@/lib/utils"; +import { DecisionTree } from "./decision-tree"; type TraceStatus = "RUNNING" | "COMPLETED" | "ERROR"; -type TabType = "decisions" | "spans" | "events"; +type TabType = "tree" | "decisions" | "spans" | "events"; interface DecisionPoint { id: string; @@ -33,6 +34,7 @@ interface DecisionPoint { contextSnapshot: Record | null; confidence: number | null; timestamp: string; + parentSpanId?: string | null; } interface Span { @@ -46,6 +48,7 @@ interface Span { input: unknown; output: unknown; metadata: Record; + parentSpanId?: string | null; } interface Event { @@ -54,6 +57,7 @@ interface Event { name: string; timestamp: string; metadata: Record; + spanId?: string | null; } interface Trace { @@ -138,7 +142,7 @@ export function TraceDetail({ spans, events, }: TraceDetailProps) { - const [activeTab, setActiveTab] = useState("decisions"); + const [activeTab, setActiveTab] = useState("tree"); const status = statusConfig[trace.status]; const StatusIcon = status.icon; @@ -243,6 +247,12 @@ export function TraceDetail({ {/* Tabs */}
+ setActiveTab("tree")} + icon={GitBranch} + label="Tree" + /> setActiveTab("decisions")} @@ -265,7 +275,15 @@ export function TraceDetail({
{/* Tab Content */} -
+
+ {activeTab === "tree" && ( + + )} {activeTab === "decisions" && ( )} diff --git a/package-lock.json b/package-lock.json index 78c1ee4..d9a89fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "version": "0.0.1", "dependencies": { "@agentlens/database": "*", + "@dagrejs/dagre": "^2.0.4", + "@xyflow/react": "^12.10.0", "lucide-react": "^0.469.0", "next": "^15.1.0", "react": "^19.0.0", @@ -29,6 +31,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4.0.0", + "@types/dagre": "^0.7.53", "@types/node": "^22.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -58,6 +61,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@dagrejs/dagre": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-2.0.4.tgz", + "integrity": "sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA==", + "license": "MIT", + "dependencies": { + "@dagrejs/graphlib": "3.0.4" + } + }, + "node_modules/@dagrejs/graphlib": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-3.0.4.tgz", + "integrity": "sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg==", + "license": "MIT" + }, "node_modules/@emnapi/runtime": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", @@ -1090,6 +1108,62 @@ "tailwindcss": "4.1.18" } }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/dagre": { + "version": "0.7.53", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.53.tgz", + "integrity": "sha512-f4gkWqzPZvYmKhOsDnhq/R8mO4UMcKdxZo+i5SCkOU1wvGeHJeUXGIHeE9pnwGyPMDof1Vx5ZQo4nxpeg2TTVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.19.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.10.tgz", @@ -1104,7 +1178,7 @@ "version": "19.2.13", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1120,6 +1194,38 @@ "@types/react": "^19.2.0" } }, + "node_modules/@xyflow/react": { + "version": "12.10.0", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.0.tgz", + "integrity": "sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.74", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.74", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.74.tgz", + "integrity": "sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/c12": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", @@ -1195,6 +1301,12 @@ "consola": "^3.2.3" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -1222,9 +1334,114 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/deepmerge-ts": { "version": "7.1.5", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", @@ -2195,6 +2412,43 @@ "dev": true, "license": "MIT" }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "packages/database": { "name": "@agentlens/database", "version": "0.0.1",