// Initialize Konva stage const stage = new Konva.Stage({ container: 'container', width: 1000, height: 600 }); const layer = new Konva.Layer(); stage.add(layer); // Store nodes, connections, and parsed data let nodes = []; let connections = []; let parsedConnections = []; let selectedNode = null; // Submit code or file for parsing function submitCode() { const fileInput = document.getElementById('codeFile'); const codeInput = document.getElementById('codeInput').value; const formData = new FormData(); if (fileInput.files.length > 0) { formData.append('file', fileInput.files[0]); } else if (codeInput) { formData.append('code', codeInput); } else { alert('Please upload a file or paste code.'); return; } fetch('/parse_code', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.error) { alert(data.error); return; } clearCanvas(); parsedConnections = data.connections; // Store for auto-connect createNodesFromParsedData(data.nodes, data.connections); }) .catch(error => console.error('Error:', error)); } // Clear existing nodes and connections function clearCanvas() { nodes.forEach(node => node.destroy()); layer.find('Shape').forEach(shape => shape.destroy()); // Includes Bezier curves nodes = []; connections = []; parsedConnections = []; layer.draw(); } // Create nodes and connections from parsed data function createNodesFromParsedData(parsedNodes, parsedConnections) { parsedNodes.forEach(nodeData => { const node = createNode( nodeData.x, nodeData.y, nodeData.label, nodeData.type, nodeData.inputs, nodeData.outputs, nodeData.id ); nodes.push(node); layer.add(node); }); // Initially, don't draw connections; wait for auto-connect or manual layer.draw(); saveNodes(); } // Create a node with inputs and outputs function createNode(x, y, label, type, inputs = [], outputs = [], id) { const node = new Konva.Group({ x: x, y: y, draggable: true }); // Node rectangle const color = type === 'function' ? '#ffeb3b' : type.includes('variable') ? '#90caf9' : type === 'import' ? '#a5d6a7' : '#ccc'; const box = new Konva.Rect({ width: 100, height: 50, fill: color, stroke: 'black', strokeWidth: 2, cornerRadius: 5 }); // Node label const text = new Konva.Text({ text: label, fontSize: 12, fontFamily: 'Arial', fill: 'black', width: 100, align: 'center', y: 20 }); node.add(box); node.add(text); // Add input/output ports inputs.forEach((input, i) => { const circle = new Konva.Circle({ x: 0, y: 10 + i * 20, radius: 5, fill: 'red' }); node.add(circle); }); outputs.forEach((output, i) => { const circle = new Konva.Circle({ x: 100, y: 10 + i * 20, radius: 5, fill: 'green' }); node.add(circle); }); // Node data node.data = { id: id, type: type, label: label, inputs: inputs, outputs: outputs, x: x, y: y }; // Handle node click for manual connections node.on('click', () => { if (!selectedNode) { selectedNode = node; } else { createSplineConnection(selectedNode, node); connections.push({ from: selectedNode.data.id, to: node.data.id }); selectedNode = null; saveNodes(); } }); // Update position and connections on drag node.on('dragmove', () => { node.data.x = node.x(); node.data.y = node.y(); updateConnections(); saveNodes(); }); return node; } // Create a spline (Bezier curve) connection function createSplineConnection(fromNode, toNode) { const startX = fromNode.x() + 100; const startY = fromNode.y() + 25; const endX = toNode.x(); const endY = toNode.y() + 25; // Control points for Bezier curve const control1X = startX + (endX - startX) / 3; const control1Y = startY; const control2X = startX + 2 * (endX - startX) / 3; const control2Y = endY; const spline = new Konva.Shape({ sceneFunc: function(context, shape) { context.beginPath(); context.moveTo(startX, startY); context.bezierCurveTo(control1X, control1Y, control2X, control2Y, endX, endY); context.fillStrokeShape(shape); }, stroke: 'black', strokeWidth: 2 }); spline.data = { from: fromNode.data.id, to: toNode.data.id }; layer.add(spline); layer.draw(); } // Auto-connect nodes based on parsed connections function autoConnect() { // Clear existing connections layer.find('Shape').forEach(shape => { if (shape.data && shape.data.from !== undefined) { shape.destroy(); } }); connections = []; // Create spline connections for parsed data parsedConnections.forEach(conn => { const fromNode = nodes.find(n => n.data.id === conn.from); const toNode = nodes.find(n => n.data.id === conn.to); if (fromNode && toNode) { createSplineConnection(fromNode, toNode); connections.push({ from: conn.from, to: conn.to }); } }); layer.draw(); saveNodes(); } // Add a manual node function addNode() { const node = createNode( Math.random() * (stage.width() - 100), Math.random() * (stage.height() - 100), 'Function', 'function', [], [], nodes.length ); nodes.push(node); layer.add(node); layer.draw(); saveNodes(); } // Update spline connections when nodes move function updateConnections() { layer.find('Shape').forEach(shape => { if (shape.data && shape.data.from !== undefined) { const fromNode = nodes.find(n => n.data.id === shape.data.from); const toNode = nodes.find(n => n.data.id === shape.data.to); if (fromNode && toNode) { const startX = fromNode.x() + 100; const startY = fromNode.y() + 25; const endX = toNode.x(); const endY = toNode.y() + 25; const control1X = startX + (endX - startX) / 3; const control1Y = startY; const control2X = startX + 2 * (endX - startX) / 3; const control2Y = endY; shape.sceneFunc(function(context, shape) { context.beginPath(); context.moveTo(startX, startY); context.bezierCurveTo(control1X, control1Y, control2X, control2Y, endX, endY); context.fillStrokeShape(shape); }); } } }); layer.draw(); } // Save nodes and connections to backend function saveNodes() { fetch('/save_nodes', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nodes: nodes.map(n => n.data), connections: connections }) }).then(response => response.json()) .then(data => console.log('Saved:', data)) .catch(error => console.error('Error:', error)); } // Initial draw layer.draw();