"use client"; import { useState } from "react"; import * as React from "react"; import { motion, AnimatePresence } from "framer-motion"; import type { Node } from "@xyflow/react"; interface VariableReferencePickerProps { nodes: Node[]; currentNodeId: string; onSelect: (reference: string) => void; } export default function VariableReferencePicker({ nodes, currentNodeId, onSelect }: VariableReferencePickerProps) { const [isOpen, setIsOpen] = useState(false); const [buttonPosition, setButtonPosition] = useState({ top: 0, right: 0 }); const buttonRef = React.useRef(null); // Get nodes that come before current node const availableNodes = nodes.filter(n => n.id !== currentNodeId); // Get input variables from Start node const startNode = nodes.find(n => (n.data as any)?.nodeType === 'start'); const inputVariables = (startNode?.data as any)?.inputVariables || []; // Build variable list with schema fields - using simple notation const nodeVariables = availableNodes.flatMap(n => { const nodeData = n.data as any; // Skip Start node - we'll add its input variables separately if (nodeData.nodeType === 'start') { return []; } // Convert node ID to simple variable name (replace hyphens with underscores) const nodeVarName = n.id.replace(/-/g, '_'); const baseVar = { name: nodeData.nodeName || n.id, path: nodeVarName, // Simple notation! description: `Output from ${nodeData.nodeName || n.id}`, nodeType: nodeData.nodeType, } as any; const vars = [baseVar]; // If node has JSON output schema, add individual field references if (nodeData.jsonOutputSchema) { try { const schema = JSON.parse(nodeData.jsonOutputSchema); if (schema.properties) { Object.keys(schema.properties).forEach(propName => { const propSchema = schema.properties[propName]; const propType = propSchema.type || 'any'; vars.push({ name: `${nodeData.nodeName || n.id}.${propName}`, path: `${nodeVarName}.${propName}`, // Simple notation! description: propSchema.description || `${propName} field from ${nodeData.nodeName || n.id}`, nodeType: nodeData.nodeType, propertyType: propType, isField: true, } as any); // If property is an object, add nested fields if (propSchema.properties) { Object.keys(propSchema.properties).forEach(nestedProp => { vars.push({ name: `${nodeData.nodeName || n.id}.${propName}.${nestedProp}`, path: `${nodeVarName}.${propName}.${nestedProp}`, description: `${nestedProp} in ${propName}`, nodeType: nodeData.nodeType, isField: true, isNested: true, } as any); }); } }); } } catch (e) { // Invalid JSON, skip } } return vars; }); // Build input variables from Start node const startNodeInputs = inputVariables.map((v: any) => ({ name: v.name, path: `input.${v.name}`, // Reference input variables as input.variableName description: v.description || `Input: ${v.name}`, type: v.type, isInputVariable: true, })); const variables = [ { category: "Input Variables", items: startNodeInputs, }, { category: "Workflow", items: [ { name: "input (full object)", path: "input", description: "All workflow inputs as object" }, { name: "lastOutput", path: "lastOutput", description: "Output from previous node" }, ], }, { category: "Previous Nodes", items: nodeVariables, }, ].filter(group => group.items.length > 0); // Remove empty groups const handleOpen = () => { if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); setButtonPosition({ top: rect.bottom + 8, right: window.innerWidth - rect.right, }); } setIsOpen(!isOpen); }; return (
{isOpen && ( <> {/* Backdrop to close on click outside */}
setIsOpen(false)} /> e.stopPropagation()} >

Available Variables

{variables.map((group, groupIndex) => (

{group.category}

{group.items.map((item: any, itemIndex) => ( ))}
))}

Click a variable to insert its reference

)}
); }