"use client"; import { motion, AnimatePresence } from "framer-motion"; import { useState } from "react"; import type { Node } from "@xyflow/react"; interface ConnectionMapperModalProps { sourceNode: Node | null; targetNode: Node | null; isOpen: boolean; onClose: () => void; onConnect: (mapping: Record) => void; } export default function ConnectionMapperModal({ sourceNode, targetNode, isOpen, onClose, onConnect, }: ConnectionMapperModalProps) { const [mapping, setMapping] = useState>({}); if (!sourceNode || !targetNode) return null; const sourceData = sourceNode.data as any; const targetData = targetNode.data as any; // Get output keys from source node const getOutputKeys = (node: Node): string[] => { const data = node.data as any; const keys: string[] = []; // Check if there's an outputSchema defined if (data.outputSchema && Array.isArray(data.outputSchema)) { keys.push(...data.outputSchema.map((field: any) => field.name)); } // Check if JSON output with schema if (data.jsonOutputSchema) { try { const schema = JSON.parse(data.jsonOutputSchema); if (schema.properties) { keys.push(...Object.keys(schema.properties)); } } catch (e) { // Invalid JSON } } // Add default keys based on node type const nodeType = data.nodeType; if (nodeType === 'mcp' || nodeType === 'firecrawl') { const outputField = data.outputField || 'full'; if (outputField === 'markdown') keys.push('markdown'); else if (outputField === 'html') keys.push('html'); else if (outputField === 'metadata') keys.push('metadata', 'metadata.title', 'metadata.description'); else if (outputField === 'results') keys.push('results[]', 'results[0]'); else if (outputField === 'urls') keys.push('urls[]'); else if (outputField === 'json') keys.push('json'); else keys.push('*'); } // If no specific keys, add common ones if (keys.length === 0) { keys.push('message', 'data', 'result'); } return keys; }; // Get input keys from target node (from arguments) const getInputKeys = (node: Node): string[] => { const data = node.data as any; if (data.arguments && Array.isArray(data.arguments)) { return data.arguments.map((arg: any) => arg.name); } return ['input']; }; const sourceKeys = getOutputKeys(sourceNode); const targetKeys = getInputKeys(targetNode); const handleConnect = () => { onConnect(mapping); onClose(); }; const handleQuickConnect = () => { // Auto-map matching field names const autoMapping: Record = {}; targetKeys.forEach(targetKey => { // Try to find exact match const exactMatch = sourceKeys.find(sk => sk === targetKey); if (exactMatch) { autoMapping[targetKey] = exactMatch; return; } // Try partial match const partialMatch = sourceKeys.find(sk => sk.toLowerCase().includes(targetKey.toLowerCase()) || targetKey.toLowerCase().includes(sk.toLowerCase()) ); if (partialMatch) { autoMapping[targetKey] = partialMatch; } }); setMapping(autoMapping); }; return ( {isOpen && ( e.stopPropagation()} className="bg-accent-white rounded-16 shadow-2xl max-w-600 w-full max-h-[80vh] overflow-y-auto" > {/* Header */}

Map Connection

{sourceData.nodeName} → {targetData.nodeName}

{/* Content */}
{/* Quick Auto-Map */} {/* Mapping Grid */}

From ({sourceData.nodeName})

To ({targetData.nodeName})

{targetKeys.map((targetKey) => (
{targetKey}
))}
{/* Preview */} {Object.keys(mapping).length > 0 && (

Connection Preview

{Object.entries(mapping).map(([target, source]) => (
{target} = {source === '__full__' ? 'entire output' : source}
))}
)}
{/* Footer */}
)}
); }