{#if !sidebarCollapsed}
Components
{/if}
{sidebarCollapsed ? '→' : '←'}
{#if !sidebarCollapsed}
{#each Object.entries(componentCategories) as [categoryName, category]}
{category.icon}
{categoryName}
{#each Object.entries(category.components) as [componentType, component]}
handleSidebarDragStart(e, componentType, component)} >
{component.icon}
{component.label}
{/each}
{/each}
{/if}
-
{Math.round(zoomLevel * 100)}%
+
⌂
Nodes: {nodes.length}
Edges: {edges.length}
🗑️ Clear
{ selectedNode = null; }} >
{#each edges as edge (edge.id)} {@const sourceNode = nodes.find(n => n.id === edge.source)} {@const targetNode = nodes.find(n => n.id === edge.target)} {#if sourceNode && targetNode} {@const points = getConnectionPoints(sourceNode, targetNode)}
deleteEdge(edge.id)} />
deleteEdge(edge.id)} > ✕
{/if} {/each} {#if isConnecting && connectionStart} {@const startNode = nodes.find(n => n.id === connectionStart)} {#if startNode}
{/if} {/if}
{#each nodes as node (node.id)} {@const config = getComponentConfig(node.type)}
handleMouseDown(e, node)} on:click={(e) => handleNodeClick(e, node)} >
{config.icon}
{node.data.display_name || node.data.label}
deleteNode(node.id)} title="Delete node" > ✕
{#if propertyFields[node.type]} {#each propertyFields[node.type].slice(0, 3) as field}
{field.label}:
{#if field.type === 'select'}
updateNodeProperty(node.id, field.key, e.target.value)} on:click|stopPropagation > {#each field.options as option}
{option}
{/each}
{:else if field.type === 'number'}
updateNodeProperty(node.id, field.key, Number(e.target.value))} on:click|stopPropagation /> {:else if field.type === 'checkbox'}
updateNodeProperty(node.id, field.key, e.target.checked)} on:click|stopPropagation />
Yes
{:else if field.type === 'textarea'}
updateNodeProperty(node.id, field.key, e.target.value)} on:click|stopPropagation rows="2" >
{:else}
updateNodeProperty(node.id, field.key, e.target.value)} on:click|stopPropagation /> {/if}
{/each} {:else}
Ready
{/if}
{#if node.data.template} {@const templateHandles = Object.entries(node.data.template).filter(([_, handle]) => handle.is_handle)} {#each templateHandles as [handleId, handle], index} {#if handle.type === 'string' || handle.type === 'object' || handle.type === 'list' || handle.type === 'file'}
(handle.type === 'object') && endConnection(e, node.id)} on:mousedown={(e) => (handle.type === 'string' || handle.type === 'list' || handle.type === 'file') && startConnection(e, node.id)} title={`${handle.display_name || handleId} (${handle.type})`} >
{/if} {/each} {@const hasInputHandles = templateHandles.some(([_, h]) => h.type === 'object')} {@const hasOutputHandles = templateHandles.some(([_, h]) => h.type === 'string' || h.type === 'list' || h.type === 'file')} {#if !hasInputHandles}
endConnection(e, node.id)} title="Input" >
{/if} {#if !hasOutputHandles}
startConnection(e, node.id)} title="Output" >
{/if} {:else}
endConnection(e, node.id)} title="Input" >
startConnection(e, node.id)} title="Output" >
{/if}
{/each}
{#if !propertyPanelCollapsed}
Properties
{/if}
{propertyPanelCollapsed ? '←' : '→'}
{#if !propertyPanelCollapsed}
{#if selectedNode && propertyFields[selectedNode.type]}
{selectedNode.data.display_name || selectedNode.data.label}
TYPE: {selectedNode.type.toUpperCase()}
{#each propertyFields[selectedNode.type] as field}
{field.label}
{#if field.help}
{field.help}
{/if} {#if field.type === 'text'}
updateNodeProperty(selectedNode.id, field.key, e.target.value)} /> {:else if field.type === 'number'}
updateNodeProperty(selectedNode.id, field.key, Number(e.target.value))} /> {:else if field.type === 'checkbox'}
updateNodeProperty(selectedNode.id, field.key, e.target.checked)} />
Enable
{:else if field.type === 'select'}
updateNodeProperty(selectedNode.id, field.key, e.target.value)} > {#each field.options as option}
{option}
{/each}
{:else if field.type === 'textarea'}
updateNodeProperty(selectedNode.id, field.key, e.target.value)} rows="4" >
{/if}
{/each}
{:else}
🎯
Select a node to edit properties
Click on any node to configure its detailed settings
{/if}
{/if}