function createNode(type, x, y) {
const template = nodeTemplates[type];
if (!template) {
console.warn('No template found for node type:', type);
return null;
}
const id = 'node-' + state.nextNodeId++;
const node = {
id,
type,
x,
y,
fields: {}
};
template.fields.forEach(field => {
node.fields[field.name] = field.default;
});
state.nodes.set(id, node);
renderNode(node);
saveToHistory();
updateStatusBar();
return node;
}
function renderNode(node) {
const template = nodeTemplates[node.type];
const typeClass = node.type.toLowerCase().replace(/\s+/g, '-');
let fieldsHtml = '';
template.fields.forEach(field => {
const value = node.fields[field.name] || '';
if (field.type === 'textarea') {
fieldsHtml += `
`;
} else if (field.type === 'select') {
const options = field.options.map(opt =>
``
).join('');
fieldsHtml += `
`;
} else {
fieldsHtml += `
`;
}
});
let portsHtml = '';
if (template.hasInput) {
portsHtml += ``;
}
if (template.hasOutput) {
portsHtml += ``;
}
if (template.hasOutputTrue) {
portsHtml += ``;
}
if (template.hasOutputFalse) {
portsHtml += ``;
}
const nodeEl = document.createElement('div');
nodeEl.className = 'node';
nodeEl.id = node.id;
nodeEl.style.left = node.x + 'px';
nodeEl.style.top = node.y + 'px';
nodeEl.innerHTML = `
${fieldsHtml}
${portsHtml}
`;
nodeEl.addEventListener('mousedown', (e) => {
if (e.target.classList.contains('node-port')) return;
selectNode(node.id);
startNodeDrag(e, node);
});
nodeEl.querySelectorAll('.node-field-input, .node-field-select').forEach(input => {
input.addEventListener('change', (e) => {
node.fields[e.target.dataset.field] = e.target.value;
saveToHistory();
});
});
nodeEl.querySelectorAll('.node-port').forEach(port => {
port.addEventListener('mousedown', (e) => {
e.stopPropagation();
startConnection(node.id, port.dataset.port);
});
port.addEventListener('mouseup', (e) => {
e.stopPropagation();
endConnection(node.id, port.dataset.port);
});
});
document.getElementById('canvas-inner').appendChild(nodeEl);
}
function getNodeIcon(type) {
const icons = {
'TALK': '',
'HEAR': '',
'SET': '',
'IF': '',
'FOR': '',
'SWITCH': '',
'CALL': '',
'SEND MAIL': '',
'GET': '',
'POST': '',
'SAVE': '',
'WAIT': '',
'SET BOT MEMORY': '',
'GET BOT MEMORY': '',
'SET USER MEMORY': '',
'GET USER MEMORY': ''
};
return icons[type] || '';
}
function startNodeDrag(e, node) {
state.isDragging = true;
const nodeEl = document.getElementById(node.id);
const startX = e.clientX;
const startY = e.clientY;
const origX = node.x;
const origY = node.y;
function onMove(e) {
const dx = (e.clientX - startX) / state.zoom;
const dy = (e.clientY - startY) / state.zoom;
node.x = snapToGrid(origX + dx);
node.y = snapToGrid(origY + dy);
nodeEl.style.left = node.x + 'px';
nodeEl.style.top = node.y + 'px';
updateConnections();
}
function onUp() {
state.isDragging = false;
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
saveToHistory();
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
function selectNode(id) {
if (state.selectedNode) {
const prevEl = document.getElementById(state.selectedNode);
if (prevEl) prevEl.classList.remove('selected');
}
state.selectedNode = id;
const nodeEl = document.getElementById(id);
if (nodeEl) nodeEl.classList.add('selected');
updatePropertiesPanel();
}
function deselectAll() {
if (state.selectedNode) {
const el = document.getElementById(state.selectedNode);
if (el) el.classList.remove('selected');
}
state.selectedNode = null;
state.selectedConnection = null;
updatePropertiesPanel();
}
function deleteSelectedNode() {
if (!state.selectedNode) return;
const nodeEl = document.getElementById(state.selectedNode);
if (nodeEl) nodeEl.remove();
state.connections = state.connections.filter(
conn => conn.from !== state.selectedNode && conn.to !== state.selectedNode
);
state.nodes.delete(state.selectedNode);
state.selectedNode = null;
updateConnections();
updatePropertiesPanel();
updateStatusBar();
saveToHistory();
}
function duplicateNode() {
if (!state.selectedNode) return;
const node = state.nodes.get(state.selectedNode);
if (!node) return;
const newNode = {...node, fields: {...node.fields}};
newNode.id = 'node-' + state.nextNodeId++;
newNode.x += 40;
newNode.y += 40;
state.nodes.set(newNode.id, newNode);
renderNode(newNode);
selectNode(newNode.id);
saveToHistory();
hideContextMenu();
}
function updateStatusBar() {
document.getElementById('node-count').textContent = state.nodes.size + ' nodes';
document.getElementById('connection-count').textContent = state.connections.length + ' connections';
}