// Initialize drag and drop functionality function initializeDragAndDrop() { const nodeItems = document.querySelectorAll('.node-item'); const canvas = document.getElementById('network-canvas'); let draggedNode = null; let offsetX, offsetY; let isDragging = false; let isConnecting = false; let startNode = null; let connectionLine = null; let nodeCounter = {}; // Track layers for proper architecture building let networkLayers = { layers: [], connections: [] }; // Add event listeners to draggable items nodeItems.forEach(item => { item.addEventListener('dragstart', handleDragStart); }); // Canvas events for dropping nodes canvas.addEventListener('dragover', handleDragOver); canvas.addEventListener('drop', handleDrop); // Handle drag start event function handleDragStart(e) { draggedNode = this; e.dataTransfer.setData('text/plain', this.getAttribute('data-type')); // Set a ghost image for drag (optional) const ghost = this.cloneNode(true); ghost.style.opacity = '0.5'; document.body.appendChild(ghost); e.dataTransfer.setDragImage(ghost, 0, 0); setTimeout(() => { document.body.removeChild(ghost); }, 0); } // Handle drag over event function handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; } // Handle drop event to create new nodes on the canvas function handleDrop(e) { e.preventDefault(); // Hide the canvas hint when nodes are added const canvasHint = document.querySelector('.canvas-hint'); if (canvasHint) { canvasHint.style.display = 'none'; } const nodeType = e.dataTransfer.getData('text/plain'); if (nodeType) { // Generate unique layer ID const layerId = window.neuralNetwork.getNextLayerId(nodeType); // Create a new node on the canvas const canvasNode = document.createElement('div'); canvasNode.className = `canvas-node ${nodeType}-node`; canvasNode.setAttribute('data-type', nodeType); canvasNode.setAttribute('data-id', layerId); // Set node position const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; canvasNode.style.left = `${x}px`; canvasNode.style.top = `${y}px`; // Get default config for this node type const nodeConfig = window.neuralNetwork.createNodeConfig(nodeType); // Create node content with input and output shape information let nodeName, inputShape, outputShape, parameters; switch(nodeType) { case 'input': nodeName = 'Input Layer'; inputShape = 'N/A'; outputShape = '[' + nodeConfig.shape.join(' × ') + ']'; parameters = nodeConfig.parameters; break; case 'hidden': const hiddenCount = document.querySelectorAll('.canvas-node[data-type="hidden"]').length; nodeConfig.units = hiddenCount === 0 ? 128 : 64; nodeName = `Hidden Layer ${hiddenCount + 1}`; // Input shape will be updated when connections are made inputShape = 'Connect input'; outputShape = `[${nodeConfig.units}]`; parameters = 'Connect input to calculate'; break; case 'output': nodeName = 'Output Layer'; inputShape = 'Connect input'; outputShape = `[${nodeConfig.units}]`; parameters = 'Connect input to calculate'; break; case 'conv': const convCount = document.querySelectorAll('.canvas-node[data-type="conv"]').length; nodeConfig.filters = 32 * (convCount + 1); nodeName = `Conv2D ${convCount + 1}`; inputShape = 'Connect input'; outputShape = 'Depends on input'; // Create parameter string parameters = `In: ?, Out: ${nodeConfig.filters}\nKernel: ${nodeConfig.kernelSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`; break; case 'pool': const poolCount = document.querySelectorAll('.canvas-node[data-type="pool"]').length; nodeName = `Pooling ${poolCount + 1}`; inputShape = 'Connect input'; outputShape = 'Depends on input'; parameters = `Pool size: ${nodeConfig.poolSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`; break; default: nodeName = 'Unknown Layer'; inputShape = 'N/A'; outputShape = 'N/A'; parameters = 'N/A'; } // Create node header const nodeHeader = document.createElement('div'); nodeHeader.className = 'node-header'; nodeHeader.textContent = nodeName; // Create node content const nodeContent = document.createElement('div'); nodeContent.className = 'node-content'; // Add shape information in a structured way const shapeInfo = document.createElement('div'); shapeInfo.className = 'shape-info'; shapeInfo.innerHTML = `
Input: ${inputShape}
Output: ${outputShape}
`; // Add parameters section const paramsSection = document.createElement('div'); paramsSection.className = 'params-section'; paramsSection.innerHTML = `
${parameters}
`; // Add connection ports const inputPort = document.createElement('div'); inputPort.className = 'port input-port'; inputPort.setAttribute('data-port-type', 'input'); const outputPort = document.createElement('div'); outputPort.className = 'port output-port'; outputPort.setAttribute('data-port-type', 'output'); // Assemble the node nodeContent.appendChild(shapeInfo); nodeContent.appendChild(paramsSection); canvasNode.appendChild(nodeHeader); canvasNode.appendChild(nodeContent); canvasNode.appendChild(inputPort); canvasNode.appendChild(outputPort); // Add node to the canvas canvas.appendChild(canvasNode); // Store node configuration canvasNode.layerConfig = nodeConfig; // Add event listeners for node manipulation canvasNode.addEventListener('mousedown', startDrag); inputPort.addEventListener('mousedown', (e) => { e.stopPropagation(); }); outputPort.addEventListener('mousedown', (e) => { e.stopPropagation(); startConnection(canvasNode, e); }); // Double-click to edit node properties canvasNode.addEventListener('dblclick', () => { openLayerEditor(canvasNode); }); // Right-click to delete canvasNode.addEventListener('contextmenu', (e) => { e.preventDefault(); deleteNode(canvasNode); }); // Add to network layers for architecture building networkLayers.layers.push({ id: layerId, type: nodeType, config: nodeConfig }); // Notify about network changes document.dispatchEvent(new CustomEvent('networkUpdated', { detail: networkLayers })); updateConnections(); } } // Start dragging an existing node on the canvas function startDrag(e) { if (isConnecting) return; // Only start drag if not clicking on buttons or ports if (e.target.closest('.node-controls') || e.target.closest('.node-port')) { return; } isDragging = true; const target = e.target.closest('.canvas-node'); const rect = target.getBoundingClientRect(); // Calculate offset offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; document.addEventListener('mousemove', dragNode); document.addEventListener('mouseup', stopDrag); // Reference to the dragged node draggedNode = target; // Make the dragged node appear on top draggedNode.style.zIndex = "100"; // Add dragging class for visual feedback draggedNode.classList.add('dragging'); // Prevent default behavior e.preventDefault(); } // Drag node on the canvas function dragNode(e) { if (!isDragging) return; const canvasRect = canvas.getBoundingClientRect(); let x = e.clientX - canvasRect.left - offsetX; let y = e.clientY - canvasRect.top - offsetY; // Constrain to canvas x = Math.max(0, Math.min(canvasRect.width - draggedNode.offsetWidth, x)); y = Math.max(0, Math.min(canvasRect.height - draggedNode.offsetHeight, y)); draggedNode.style.left = `${x}px`; draggedNode.style.top = `${y}px`; // Update node position in network layers const nodeId = draggedNode.getAttribute('data-id'); const layerIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId); if (layerIndex !== -1) { networkLayers.layers[layerIndex].position = { x, y }; } // Update connected lines if any updateConnections(); } // Stop dragging function stopDrag() { if (!isDragging) return; isDragging = false; document.removeEventListener('mousemove', dragNode); document.removeEventListener('mouseup', stopDrag); // Reset z-index and remove dragging class if (draggedNode) { draggedNode.style.zIndex = "10"; draggedNode.classList.remove('dragging'); // Trigger connections update one more time updateConnections(); } } // Start creating a connection between nodes function startConnection(node, e) { isConnecting = true; startNode = node; // Create a temporary line connectionLine = document.createElement('div'); connectionLine.className = 'connection temp-connection'; // Get start position (center of the port) const portOut = node.querySelector('.port-out'); const portRect = portOut.getBoundingClientRect(); const canvasRect = canvas.getBoundingClientRect(); const startX = portRect.left + portRect.width / 2 - canvasRect.left; const startY = portRect.top + portRect.height / 2 - canvasRect.top; // Position the line connectionLine.style.left = `${startX}px`; connectionLine.style.top = `${startY}px`; connectionLine.style.width = '0px'; connectionLine.style.transform = 'rotate(0deg)'; // Add active class to the starting port portOut.classList.add('active-port'); // Highlight valid target ports highlightValidConnectionTargets(node); canvas.appendChild(connectionLine); // Add event listeners for drawing the line document.addEventListener('mousemove', drawConnection); document.addEventListener('mouseup', cancelConnection); e.preventDefault(); } // Highlight valid targets for connection function highlightValidConnectionTargets(sourceNode) { const sourceType = sourceNode.getAttribute('data-type'); const sourceId = sourceNode.getAttribute('data-id'); document.querySelectorAll('.canvas-node').forEach(node => { if (node !== sourceNode) { const nodeType = node.getAttribute('data-type'); const nodeId = node.getAttribute('data-id'); const isValidTarget = isValidConnection(sourceType, nodeType, sourceId, nodeId); const portIn = node.querySelector('.port-in'); if (isValidTarget) { portIn.classList.add('valid-target'); } else { portIn.classList.add('invalid-target'); } } }); } // Remove highlights from all ports function removePortHighlights() { document.querySelectorAll('.port-in, .port-out').forEach(port => { port.classList.remove('active-port', 'valid-target', 'invalid-target'); }); } // Check if a connection between two node types is valid function isValidConnection(sourceType, targetType, sourceId, targetId) { // Basic hierarchy validation if (sourceType === 'output' || targetType === 'input') { return false; // Output can't have outgoing connections, Input can't have incoming } // Prevent cycles const existingConnection = networkLayers.connections.find( conn => conn.target === sourceId && conn.source === targetId ); if (existingConnection) { return false; } // Specific connection rules switch(sourceType) { case 'input': return ['hidden', 'conv'].includes(targetType); case 'conv': return ['conv', 'pool', 'hidden'].includes(targetType); case 'pool': return ['conv', 'hidden'].includes(targetType); case 'hidden': return ['hidden', 'output'].includes(targetType); default: return false; } } // Draw the connection line as mouse moves function drawConnection(e) { if (!isConnecting || !connectionLine) return; const canvasRect = canvas.getBoundingClientRect(); const portOut = startNode.querySelector('.port-out'); const portRect = portOut.getBoundingClientRect(); // Calculate start and end points const startX = portRect.left + portRect.width / 2 - canvasRect.left; const startY = portRect.top + portRect.height / 2 - canvasRect.top; const endX = e.clientX - canvasRect.left; const endY = e.clientY - canvasRect.top; // Calculate length and angle const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2)); const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI; // Update line connectionLine.style.width = `${length}px`; connectionLine.style.transform = `rotate(${angle}deg)`; // Highlight the port under cursor document.querySelectorAll('.canvas-node').forEach(node => { if (node !== startNode) { const nodeRect = node.getBoundingClientRect(); const portIn = node.querySelector('.port-in'); const portInRect = portIn.getBoundingClientRect(); // Check if mouse is over the input port if (e.clientX >= portInRect.left && e.clientX <= portInRect.right && e.clientY >= portInRect.top && e.clientY <= portInRect.bottom) { portIn.classList.add('port-hover'); } else { portIn.classList.remove('port-hover'); } } }); } // Cancel connection creation function cancelConnection(e) { if (!isConnecting) return; // Find if we're over a valid input port let targetNode = null; document.querySelectorAll('.canvas-node').forEach(node => { if (node !== startNode) { const portIn = node.querySelector('.port-in'); const portRect = portIn.getBoundingClientRect(); if (e.clientX >= portRect.left && e.clientX <= portRect.right && e.clientY >= portRect.top && e.clientY <= portRect.bottom) { // Check if this would be a valid connection const sourceType = startNode.getAttribute('data-type'); const targetType = node.getAttribute('data-type'); const sourceId = startNode.getAttribute('data-id'); const targetId = node.getAttribute('data-id'); if (isValidConnection(sourceType, targetType, sourceId, targetId)) { targetNode = node; } } } }); // If we found a valid target, create the connection if (targetNode) { endConnection(targetNode); } else { // Otherwise, remove the temporary line if (connectionLine && connectionLine.parentNode) { connectionLine.parentNode.removeChild(connectionLine); } } // Remove all port highlights removePortHighlights(); document.querySelectorAll('.port-hover').forEach(port => { port.classList.remove('port-hover'); }); // Reset variables isConnecting = false; startNode = null; connectionLine = null; // Remove event listeners document.removeEventListener('mousemove', drawConnection); document.removeEventListener('mouseup', cancelConnection); } // End creating a connection function endConnection(targetNode) { if (!isConnecting || !connectionLine || !startNode) return; const sourceType = startNode.getAttribute('data-type'); const targetType = targetNode.getAttribute('data-type'); const sourceId = startNode.getAttribute('data-id'); const targetId = targetNode.getAttribute('data-id'); // Check if this is a valid connection if (isValidConnection(sourceType, targetType, sourceId, targetId)) { // Create a permanent SVG connection const canvas = document.getElementById('network-canvas'); const svgContainer = document.querySelector('#network-canvas .svg-container') || createSVGContainer(); // Get positions for source and target nodes const sourceRect = startNode.getBoundingClientRect(); const targetRect = targetNode.getBoundingClientRect(); const canvasRect = canvas.getBoundingClientRect(); // Calculate port positions const sourcePort = startNode.querySelector('.output-port'); const targetPort = targetNode.querySelector('.input-port'); const sourcePortRect = sourcePort.getBoundingClientRect(); const targetPortRect = targetPort.getBoundingClientRect(); const startX = sourcePortRect.left + (sourcePortRect.width / 2) - canvasRect.left; const startY = sourcePortRect.top + (sourcePortRect.height / 2) - canvasRect.top; const endX = targetPortRect.left + (targetPortRect.width / 2) - canvasRect.left; const endY = targetPortRect.top + (targetPortRect.height / 2) - canvasRect.top; // Create the connection const pathId = `connection-${sourceId}-${targetId}`; const connectionPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); connectionPath.setAttribute('id', pathId); connectionPath.setAttribute('class', 'connection-line'); // Curved path (bezier) const dx = Math.abs(endX - startX) * 0.7; const path = `M ${startX} ${startY} C ${startX + dx} ${startY}, ${endX - dx} ${endY}, ${endX} ${endY}`; connectionPath.setAttribute('d', path); // Add connection to SVG container svgContainer.appendChild(connectionPath); // Add to connections networkLayers.connections.push({ id: pathId, source: sourceId, target: targetId, sourceType: sourceType, targetType: targetType }); // Update input and output shapes updateNodeShapes(sourceId, targetId); // Notify about connection document.dispatchEvent(new CustomEvent('networkUpdated', { detail: networkLayers })); } // Clean up removePortHighlights(); if (connectionLine) { connectionLine.remove(); connectionLine = null; } isConnecting = false; startNode = null; } // Update input and output shapes when connections are made function updateNodeShapes(sourceId, targetId) { const sourceNode = document.querySelector(`.canvas-node[data-id="${sourceId}"]`); const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`); if (sourceNode && targetNode) { const sourceConfig = sourceNode.layerConfig; const targetConfig = targetNode.layerConfig; // Update the target's input shape based on the source's output shape if (sourceConfig && targetConfig) { // Calculate output shape based on node type let outputShape; switch (sourceNode.getAttribute('data-type')) { case 'input': outputShape = sourceConfig.shape; break; case 'hidden': outputShape = [sourceConfig.units]; break; case 'output': outputShape = [sourceConfig.units]; break; case 'conv': // For Conv2D, the output shape depends on the input and parameters // This is a simplified calculation if (targetConfig.inputShape) { const h = targetConfig.inputShape[0]; const w = targetConfig.inputShape[1]; const kh = sourceConfig.kernelSize[0]; const kw = sourceConfig.kernelSize[1]; const sh = sourceConfig.strides[0]; const sw = sourceConfig.strides[1]; const padding = sourceConfig.padding; let outHeight, outWidth; if (padding === 'same') { outHeight = Math.ceil(h / sh); outWidth = Math.ceil(w / sw); } else { // 'valid' outHeight = Math.ceil((h - kh + 1) / sh); outWidth = Math.ceil((w - kw + 1) / sw); } outputShape = [outHeight, outWidth, sourceConfig.filters]; } else { outputShape = ['?', '?', sourceConfig.filters]; } break; case 'pool': // For pooling, also depends on the input and parameters if (targetConfig.inputShape) { const h = targetConfig.inputShape[0]; const w = targetConfig.inputShape[1]; const c = targetConfig.inputShape[2]; const ph = sourceConfig.poolSize[0]; const pw = sourceConfig.poolSize[1]; const sh = sourceConfig.strides[0]; const sw = sourceConfig.strides[1]; const padding = sourceConfig.padding; let outHeight, outWidth; if (padding === 'same') { outHeight = Math.ceil(h / sh); outWidth = Math.ceil(w / sw); } else { // 'valid' outHeight = Math.ceil((h - ph + 1) / sh); outWidth = Math.ceil((w - pw + 1) / sw); } outputShape = [outHeight, outWidth, c]; } else { outputShape = ['?', '?', '?']; } break; case 'linear': outputShape = [sourceConfig.outputFeatures]; break; default: outputShape = ['?', '?', '?']; } // Update the target's input shape targetConfig.inputShape = outputShape; // Update UI updateNodeDisplayShapes(sourceNode, targetNode); } } } // Update the displayed shapes in the UI function updateNodeDisplayShapes(sourceNode, targetNode) { if (sourceNode && targetNode) { const sourceType = sourceNode.getAttribute('data-type'); const targetType = targetNode.getAttribute('data-type'); const sourceConfig = sourceNode.layerConfig; const targetConfig = targetNode.layerConfig; // Update source node output shape display const sourceOutputElem = sourceNode.querySelector('.output-shape'); if (sourceOutputElem && sourceConfig) { let outputText; switch (sourceType) { case 'input': outputText = `[${sourceConfig.shape.join(' × ')}]`; break; case 'hidden': case 'output': outputText = `[${sourceConfig.units}]`; break; case 'conv': if (sourceConfig.outputShape) { outputText = `[${sourceConfig.outputShape.join(' × ')}]`; } else { outputText = `[? × ? × ${sourceConfig.filters}]`; } break; case 'pool': if (sourceConfig.outputShape) { outputText = `[${sourceConfig.outputShape.join(' × ')}]`; } else { outputText = 'Depends on input'; } break; case 'linear': outputText = `[${sourceConfig.outputFeatures}]`; break; default: outputText = 'Unknown'; } sourceOutputElem.textContent = outputText; } // Update target node input shape display const targetInputElem = targetNode.querySelector('.input-shape'); if (targetInputElem && targetConfig && targetConfig.inputShape) { targetInputElem.textContent = `[${targetConfig.inputShape.join(' × ')}]`; // Update parameters section const targetParamsElem = targetNode.querySelector('.params-display'); if (targetParamsElem) { // Calculate and display parameters let paramsText = ''; switch (targetType) { case 'hidden': const inputUnits = Array.isArray(targetConfig.inputShape) ? targetConfig.inputShape.reduce((acc, val) => acc * val, 1) : targetConfig.inputShape; const biasParams = targetConfig.useBias ? targetConfig.units : 0; const totalParams = (inputUnits * targetConfig.units) + biasParams; paramsText = `In: ${inputUnits}, Out: ${targetConfig.units}\nParams: ${totalParams.toLocaleString()}\nDropout: ${targetConfig.dropoutRate}`; break; case 'output': const outInputUnits = Array.isArray(targetConfig.inputShape) ? targetConfig.inputShape.reduce((acc, val) => acc * val, 1) : targetConfig.inputShape; const outBiasParams = targetConfig.useBias ? targetConfig.units : 0; const outTotalParams = (outInputUnits * targetConfig.units) + outBiasParams; paramsText = `In: ${outInputUnits}, Out: ${targetConfig.units}\nParams: ${outTotalParams.toLocaleString()}\nActivation: ${targetConfig.activation}`; break; case 'conv': const channels = targetConfig.inputShape[2] || '?'; const kernelH = targetConfig.kernelSize[0]; const kernelW = targetConfig.kernelSize[1]; const kernelParams = kernelH * kernelW * channels * targetConfig.filters; const convBiasParams = targetConfig.useBias ? targetConfig.filters : 0; const convTotalParams = kernelParams + convBiasParams; paramsText = `In: ${channels}, Out: ${targetConfig.filters}\nKernel: ${targetConfig.kernelSize.join('×')}\nStride: ${targetConfig.strides.join('×')}\nPadding: ${targetConfig.padding}\nParams: ${convTotalParams.toLocaleString()}`; break; case 'pool': paramsText = `Pool size: ${targetConfig.poolSize.join('×')}\nStride: ${targetConfig.strides.join('×')}\nPadding: ${targetConfig.padding}\nParams: 0`; break; case 'linear': const linearInputs = targetConfig.inputFeatures; const linearBiasParams = targetConfig.useBias ? targetConfig.outputFeatures : 0; const linearTotalParams = (linearInputs * targetConfig.outputFeatures) + linearBiasParams; paramsText = `In: ${linearInputs}, Out: ${targetConfig.outputFeatures}\nParams: ${linearTotalParams.toLocaleString()}\nLearning Rate: ${targetConfig.learningRate}\nLoss: ${targetConfig.lossFunction}`; break; } targetParamsElem.textContent = paramsText; } } } } // Delete a node and its connections function deleteNode(node) { if (!node) return; const nodeId = node.getAttribute('data-id'); // Remove all connections to/from this node document.querySelectorAll(`.connection[data-source="${nodeId}"], .connection[data-target="${nodeId}"]`).forEach(conn => { conn.parentNode.removeChild(conn); }); // Remove from network layers networkLayers.layers = networkLayers.layers.filter(layer => layer.id !== nodeId); networkLayers.connections = networkLayers.connections.filter(conn => conn.source !== nodeId && conn.target !== nodeId ); // Remove the node node.parentNode.removeChild(node); // Update layer connectivity updateLayerConnectivity(); } // Open layer editor modal function openLayerEditor(node) { if (!node) return; const nodeId = node.getAttribute('data-id'); const nodeType = node.getAttribute('data-type'); const nodeName = node.getAttribute('data-name'); const dimensions = node.getAttribute('data-dimensions'); // Trigger custom event const event = new CustomEvent('openLayerEditor', { detail: { id: nodeId, type: nodeType, name: nodeName, dimensions: dimensions } }); document.dispatchEvent(event); } // Update connections when nodes are moved function updateConnections() { const connections = document.querySelectorAll('.connection'); connections.forEach(connection => { const sourceId = connection.getAttribute('data-source'); const targetId = connection.getAttribute('data-target'); const sourceNode = document.querySelector(`.canvas-node[data-id="${sourceId}"]`); const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`); if (sourceNode && targetNode) { const sourcePort = sourceNode.querySelector('.port-out'); const targetPort = targetNode.querySelector('.port-in'); if (sourcePort && targetPort) { const sourceRect = sourcePort.getBoundingClientRect(); const targetRect = targetPort.getBoundingClientRect(); const canvasRect = canvas.getBoundingClientRect(); const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left; const startY = sourceRect.top + sourceRect.height / 2 - canvasRect.top; const endX = targetRect.left + targetRect.width / 2 - canvasRect.left; const endY = targetRect.top + targetRect.height / 2 - canvasRect.top; const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2)); const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI; connection.style.left = `${startX}px`; connection.style.top = `${startY}px`; connection.style.width = `${length}px`; connection.style.transform = `rotate(${angle}deg)`; } } else { // If either node is missing, remove the connection if (connection.parentNode) { connection.parentNode.removeChild(connection); // Remove from the connections array const connIndex = networkLayers.connections.findIndex(conn => conn.source === sourceId && conn.target === targetId ); if (connIndex !== -1) { networkLayers.connections.splice(connIndex, 1); } } } }); } // Get the current network architecture function getNetworkArchitecture() { return networkLayers; } // Clear all nodes from the canvas function clearAllNodes() { // Clear all nodes and connections document.querySelectorAll('.canvas-node, .connection').forEach(el => { el.parentNode.removeChild(el); }); // Reset network layers networkLayers = { layers: [], connections: [] }; // Reset layer counter window.neuralNetwork.resetLayerCounter(); // Show the canvas hint const canvasHint = document.querySelector('.canvas-hint'); if (canvasHint) { canvasHint.style.display = 'block'; } // Trigger network updated event const event = new CustomEvent('networkUpdated', { detail: networkLayers }); document.dispatchEvent(event); } // Export functions window.dragDrop = { getNetworkArchitecture, clearAllNodes, updateConnections }; }