broadfield-dev commited on
Commit
5b20ea5
·
verified ·
1 Parent(s): 58dff6d

Update static/canvas.js

Browse files
Files changed (1) hide show
  1. static/canvas.js +119 -688
static/canvas.js CHANGED
@@ -1,727 +1,158 @@
1
- // Initialize Konva stage
2
  const stage = new Konva.Stage({
3
  container: 'container',
4
- width: 1000,
5
- height: 600,
6
  draggable: true
7
  });
8
 
9
  const layer = new Konva.Layer();
10
  stage.add(layer);
11
 
12
- // Store nodes, connections, and parsed data
13
- let nodes = [];
14
- let connections = [];
15
- let parsedConnections = [];
16
- let selectedPort = null;
17
- let disconnectMode = false;
18
- let codeWindow = null; // Track open code window
19
- let codeTextarea = null; // Track textarea element
20
-
21
- // Zoom functionality
22
  let scale = 1;
23
- const scaleFactor = 1.1;
24
- const minScale = 0.5;
25
- const maxScale = 2.0;
26
-
27
  stage.on('wheel', (e) => {
28
  e.evt.preventDefault();
29
- const oldScale = scale;
30
  const pointer = stage.getPointerPosition();
31
- const delta = e.evt.deltaY > 0 ? 1 / scaleFactor : scaleFactor;
32
- const newScale = Math.max(minScale, Math.min(maxScale, oldScale * delta));
33
- if (newScale === scale) return;
34
- scale = newScale;
35
- const mousePointTo = {
36
- x: (pointer.x - stage.x()) / oldScale,
37
- y: (pointer.y - stage.y()) / oldScale
38
- };
39
- stage.scale({ x: scale, y: scale });
40
  const newPos = {
41
- x: pointer.x - mousePointTo.x * scale,
42
- y: pointer.y - mousePointTo.y * scale
43
  };
44
  stage.position(newPos);
45
- stage.batchDraw();
46
  });
47
 
48
- // Submit code or file for parsing
49
- function submitCode() {
50
- const fileInput = document.getElementById('codeFile');
51
- const codeInput = document.getElementById('codeInput').value;
52
- const formData = new FormData();
53
-
54
- if (fileInput.files.length > 0) {
55
- formData.append('file', fileInput.files[0]);
56
- } else if (codeInput) {
57
- formData.append('code', codeInput);
58
- } else {
59
- alert('Please upload a file or paste code.');
60
- return;
61
- }
62
-
63
- fetch('/parse_code', {
64
  method: 'POST',
65
- body: formData
 
66
  })
67
- .then(response => response.json())
68
  .then(data => {
69
- if (data.error) {
70
- alert(data.error);
71
- return;
72
- }
73
- clearCanvas();
74
- parsedConnections = data.connections;
75
- createNodesFromParsedData(data.nodes, data.connections);
76
- })
77
- .catch(error => console.error('Error:', error));
78
- }
79
-
80
- // Clear existing nodes and connections
81
- function clearCanvas() {
82
- nodes.forEach(node => node.destroy());
83
- layer.find('Shape').forEach(shape => shape.destroy());
84
- nodes = [];
85
- connections = [];
86
- parsedConnections = [];
87
- if (codeWindow) {
88
- codeWindow.destroy();
89
- codeWindow = null;
90
- }
91
- if (codeTextarea) {
92
- codeTextarea.remove();
93
- codeTextarea = null;
94
- }
95
- layer.draw();
96
- }
97
-
98
- // Create nodes and connections from parsed data
99
- function createNodesFromParsedData(parsedNodes, parsedConnections) {
100
- const columns = {
101
- imports: { x: 50, y: 50, count: 0 }, // Column for imports
102
- global: { x: 250, y: 50, count: 0 }, // Column for global scope
103
- functions: {} // Columns for functions
104
- };
105
-
106
- // First pass: Assign function nodes to columns
107
- parsedNodes.forEach(nodeData => {
108
- if (nodeData.type === 'function') {
109
- const functionId = nodeData.id || `Function_${Object.keys(columns.functions).length + 1}`;
110
- columns.functions[functionId] = {
111
- x: 450 + Object.keys(columns.functions).length * 200,
112
- y: 50,
113
- count: 0
114
- };
115
- }
116
  });
117
-
118
- // Second pass: Create nodes with column-based positioning
119
- parsedNodes.forEach(nodeData => {
120
- const parentPath = nodeData.parent_path || 'global';
121
- const level = nodeData.level || 0;
122
- let x, y;
123
-
124
- if (nodeData.type === 'import') {
125
- // Imports column
126
- x = columns.imports.x;
127
- y = columns.imports.y + columns.imports.count * 80;
128
- columns.imports.count++;
129
- } else if (nodeData.type === 'function') {
130
- // Function column
131
- const functionId = nodeData.id;
132
- x = columns.functions[functionId].x;
133
- y = columns.functions[functionId].y + columns.functions[functionId].count * 80;
134
- columns.functions[functionId].count++;
135
- } else if (parentPath !== 'global' && parentPath.includes('Function')) {
136
- // Child of a function
137
- const functionId = parentPath.split(' -> ')[0];
138
- if (columns.functions[functionId]) {
139
- x = columns.functions[functionId].x;
140
- y = columns.functions[functionId].y + columns.functions[functionId].count * 80;
141
- columns.functions[functionId].count++;
142
- } else {
143
- // Fallback to global if function not found
144
- x = columns.global.x;
145
- y = columns.global.y + columns.global.count * 80;
146
- columns.global.count++;
147
- }
148
- } else {
149
- // Global scope (non-import, non-function)
150
- x = columns.global.x;
151
- y = columns.global.y + columns.global.count * 80;
152
- columns.global.count++;
153
- }
154
-
155
- const node = createNode(
156
- x,
157
- y,
158
- nodeData.label || 'Unnamed',
159
- nodeData.type || 'other',
160
- nodeData.inputs || [],
161
- nodeData.outputs || [],
162
- nodeData.id || `node_${nodes.length}`,
163
- nodeData.source || '',
164
- parentPath,
165
- level
166
- );
167
- nodes.push(node);
168
- layer.add(node);
169
- });
170
-
171
- layer.draw();
172
- autoConnect();
173
- saveNodes();
174
  }
175
 
176
- // Create a node with inputs, outputs, and code segment
177
- function createNode(x, y, label, type, inputs = [], outputs = [], id, source = '', parent_path = 'global', level = 0) {
178
- const node = new Konva.Group({
179
- x: x,
180
- y: y,
181
- draggable: true
182
- });
183
-
184
- // Node rectangle
185
- const isNumberBox = type === 'number_box';
186
- const color = isNumberBox ? '#ffcccb' : type === 'function' ? '#ffeb3b' : type.includes('variable') ? '#90caf9' : type === 'import' ? '#a5d6a7' : '#ccc';
187
- const width = isNumberBox ? 80 : 100;
188
- const height = isNumberBox ? 40 : 50;
189
-
190
- const box = new Konva.Rect({
191
- width: width,
192
- height: height,
193
- fill: color,
194
- stroke: 'black',
195
- strokeWidth: 2,
196
- cornerRadius: 5
197
- });
198
-
199
- // Node label
200
- const text = new Konva.Text({
201
- text: label,
202
- fontSize: 12,
203
- fontFamily: 'Arial',
204
- fill: 'black',
205
- width: width,
206
- align: 'center',
207
- y: isNumberBox ? 14 : 20
208
- });
209
-
210
- node.add(box);
211
- node.add(text);
212
-
213
- // Input/output ports
214
- const inputPorts = inputs.map((input, i) => ({
215
- id: `input-${id}-${i}`,
216
- name: input,
217
- circle: new Konva.Circle({
218
- x: 0,
219
- y: 10 + i * 20,
220
- radius: 5,
221
- fill: 'red'
222
- })
223
- }));
224
-
225
- const outputPorts = outputs.map((output, i) => ({
226
- id: `output-${id}-${i}`,
227
- name: output,
228
- circle: new Konva.Circle({
229
- x: width,
230
- y: 10 + i * 20,
231
- radius: 5,
232
- fill: 'green'
233
- })
234
- }));
235
-
236
- // Add ports to node and set up click handlers
237
- inputPorts.forEach(port => {
238
- node.add(port.circle);
239
- port.circle.on('click', () => {
240
- if (!selectedPort) {
241
- selectedPort = { node, portId: port.id, type: 'input' };
242
- disconnectMode = true;
243
- } else if (selectedPort.type === 'output' && selectedPort.node !== node) {
244
- createSplineConnection(
245
- selectedPort.node,
246
- selectedPort.portId,
247
- node,
248
- port.id
249
- );
250
- connections.push({
251
- fromNodeId: selectedPort.node.data.id,
252
- fromPortId: selectedPort.portId,
253
- toNodeId: node.data.id,
254
- toPortId: port.id
255
- });
256
- selectedPort = null;
257
- disconnectMode = false;
258
- saveNodes();
259
- } else if (disconnectMode && selectedPort.type === 'input' && selectedPort.node === node) {
260
- selectedPort = { node, portId: port.id, type: 'input' };
261
- } else {
262
- selectedPort = null;
263
- disconnectMode = false;
264
- }
265
- });
266
- });
267
-
268
- outputPorts.forEach(port => {
269
- node.add(port.circle);
270
- port.circle.on('click', () => {
271
- if (!selectedPort) {
272
- selectedPort = { node, portId: port.id, type: 'output' };
273
- disconnectMode = false;
274
- } else if (disconnectMode && selectedPort.type === 'input') {
275
- const connIndex = connections.findIndex(
276
- c => c.toNodeId === selectedPort.node.data.id &&
277
- c.toPortId === selectedPort.portId &&
278
- c.fromNodeId === node.data.id &&
279
- c.fromPortId === port.id
280
- );
281
- if (connIndex !== -1) {
282
- const conn = connections[connIndex];
283
- const spline = layer.find('Shape').find(s =>
284
- s.data.fromNodeId === conn.fromNodeId &&
285
- s.data.fromPortId === conn.fromPortId &&
286
- s.data.toNodeId === conn.toNodeId &&
287
- s.data.toPortId === conn.toPortId
288
- );
289
- if (spline) spline.destroy();
290
- connections.splice(connIndex, 1);
291
- layer.draw();
292
- saveNodes();
293
- }
294
- selectedPort = null;
295
- disconnectMode = false;
296
- } else {
297
- selectedPort = null;
298
- disconnectMode = false;
299
- }
300
- });
301
- });
302
-
303
- // Node data
304
- node.data = {
305
- id: id,
306
- type: type,
307
- label: label,
308
- inputs: inputPorts,
309
- outputs: outputPorts,
310
- x: x,
311
- y: y,
312
- source: source,
313
- parent_path: parent_path,
314
- level: level
315
- };
316
-
317
- // Click handler to show code window
318
- box.on('click', () => {
319
- if (codeWindow) {
320
- codeWindow.destroy();
321
- codeWindow = null;
322
- }
323
- if (codeTextarea) {
324
- codeTextarea.remove();
325
- codeTextarea = null;
326
- }
327
-
328
- const nodePos = node.getAbsolutePosition();
329
-
330
- // Create textarea for editing
331
- codeTextarea = document.createElement('textarea');
332
- codeTextarea.style.position = 'relative';
333
- const canvasRect = stage.container().getBoundingClientRect();
334
- const textareaX = nodePos.x;
335
- // const textareaX = (nodePos.x + stage.x()) / scale + canvasRect.left;
336
- // const textareaY = (nodePos.y + height + 10 + stage.y()) / scale + canvasRect.top;
337
- const textareaY = nodePos.y;
338
- //codeTextarea.style.right = `${textareaX}px`;
339
- //codeTextarea.style.top = `${textareaY}px`;
340
- codeTextarea.style.left = '15px' || `${textareaX}px`;
341
- codeTextarea.style.top = '-200px' || `${textareaY}px`;
342
- codeTextarea.style.width = '300px' || `${300 / scale}px`;
343
- codeTextarea.style.height = '200px' || `${100 / scale}px`;
344
- codeTextarea.style.fontFamily = 'monospace';
345
- codeTextarea.style.fontSize = '12px' || `${12 / scale}px`;
346
- codeTextarea.style.background = '#ebebeb';
347
- codeTextarea.style.border = 'solid';
348
- codeTextarea.style.borderColor = 'grey';
349
- codeTextarea.style.resize = 'none';
350
- codeTextarea.value = source || '';
351
- document.body.appendChild(codeTextarea);
352
- codeWindow = new Konva.Group({
353
- x: node.x(),
354
- y: node.y() + height + 10
355
- });
356
-
357
- const codeBox = new Konva.Rect({
358
- width: 300,
359
- height: 100,
360
- fill: '#f0f0f0',
361
- stroke: 'black',
362
- strokeWidth: 1,
363
- cornerRadius: 5
364
- });
365
-
366
- // Display source in Konva Text for visual containment
367
- const codeText = new Konva.Text({
368
- x: 5,
369
- y: 5,
370
- text: source || '',
371
- fontSize: 12,
372
- fontFamily: 'monospace',
373
- fill: 'black',
374
- width: 290,
375
- padding: 5
376
- });
377
-
378
- //codeWindow.add(codeBox);
379
- //codeWindow.add(codeText);
380
-
381
-
382
-
383
- codeWindow.add(codeBox);
384
- codeWindow.add(codeText);
385
- //codeWindow.add(codeTextarea);
386
- layer.add(codeWindow);
387
- // Update code on change
388
- codeTextarea.addEventListener('change', () => {
389
- const newSource = codeTextarea.value;
390
- node.data.source = newSource;
391
- codeText.text(newSource);
392
- fetch('/update_node', {
393
- method: 'POST',
394
- headers: { 'Content-Type': 'application/json' },
395
- body: JSON.stringify({
396
- id: node.data.id,
397
- source: newSource
398
- })
399
- })
400
- .then(response => response.json())
401
- .then(data => {
402
- if (data.error) {
403
- alert(data.error);
404
- } else {
405
- console.log('Node updated:', data);
406
- updateProgram();
407
- }
408
- })
409
- .catch(error => console.error('Error:', error));
410
- });
411
-
412
- // Close window on click outside
413
- stage.on('click', (e) => {
414
- if (e.target !== box && codeWindow) {
415
- codeWindow.destroy();
416
- codeWindow = null;
417
- if (codeTextarea) {
418
- codeTextarea.remove();
419
- codeTextarea = null;
420
- }
421
- stage.off('click');
422
- }
423
- });
424
-
425
- layer.draw();
426
- });
427
-
428
- // Update position and connections on drag
429
- node.on('dragmove', () => {
430
- node.data.x = node.x();
431
- node.data.y = node.y();
432
- if (codeWindow && codeTextarea) {
433
- const nodePos = node.getAbsolutePosition();
434
- codeWindow.position({ x: node.x(), y: node.y() + height + 10 });
435
- const canvasRect = stage.container().getBoundingClientRect();
436
- const textareaX = (nodePos.x + stage.x()) / scale + canvasRect.left;
437
- const textareaY = (nodePos.y + height + 10 + stage.y()) / scale + canvasRect.top;
438
- codeTextarea.style.left = `${textareaX}px`;
439
- codeTextarea.style.top = `${textareaY}px`;
440
- }
441
- updateConnections();
442
- saveNodes();
443
- });
444
-
445
- return node;
446
- }
447
-
448
- // Create a spline connection
449
- function createSplineConnection(fromNode, fromPortId, toNode, toPortId) {
450
- const fromPort = fromNode.data.outputs.find(p => p.id === fromPortId);
451
- const toPort = toNode.data.inputs.find(p => p.id === toPortId);
452
- if (!fromPort || !toPort) return;
453
-
454
- const startX = fromNode.x() + fromPort.circle.x();
455
- const startY = fromNode.y() + fromPort.circle.y();
456
- const endX = toNode.x() + toPort.circle.x();
457
- const endY = toNode.y() + toPort.circle.y();
458
-
459
- const control1X = startX + (endX - startX) / 3;
460
- const control1Y = startY;
461
- const control2X = startX + 2 * (endX - startX) / 3;
462
- const control2Y = endY;
463
-
464
- const spline = new Konva.Shape({
465
- sceneFunc: function(context, shape) {
466
- context.beginPath();
467
- context.moveTo(startX, startY);
468
- context.bezierCurveTo(control1X, control1Y, control2X, control2Y, endX, endY);
469
- context.fillStrokeShape(shape);
470
- },
471
- stroke: 'black',
472
- strokeWidth: 2
473
- });
474
-
475
- spline.data = {
476
- fromNodeId: fromNode.data.id,
477
- fromPortId: fromPortId,
478
- toNodeId: toNode.data.id,
479
- toPortId: toPortId
480
- };
481
- layer.add(spline);
482
- layer.draw();
483
- }
484
-
485
- // Enhanced auto-connect based on hierarchy, position, and role
486
- function autoConnect() {
487
- layer.find('Shape').forEach(shape => {
488
- if (shape.data && shape.data.fromNodeId !== undefined) {
489
- shape.destroy();
490
- }
491
- });
492
- connections = [];
493
-
494
- const sortedNodes = [...nodes].sort((a, b) => {
495
- if (a.data.level !== b.data.level) return a.data.level - b.data.level;
496
- return a.data.y - b.data.y; // Sort by y within columns
497
- });
498
-
499
- const hierarchy = {};
500
- sortedNodes.forEach(node => {
501
- const parent = node.data.parent_path.split(' -> ')[0] || 'global';
502
- if (!hierarchy[parent]) hierarchy[parent] = [];
503
- hierarchy[parent].push(node);
504
- });
505
-
506
- parsedConnections.forEach(conn => {
507
- const fromNode = nodes.find(n => n.data.id === conn.from);
508
- const toNode = nodes.find(n => n.data.id === conn.to);
509
- if (fromNode && toNode) {
510
- const fromPort = fromNode.data.outputs[0];
511
- const toPort = toNode.data.inputs[0];
512
- if (fromPort && toPort) {
513
- createSplineConnection(fromNode, fromPort.id, toNode, toPort.id);
514
- connections.push({
515
- fromNodeId: fromNode.data.id,
516
- fromPortId: fromPort.id,
517
- toNodeId: toNode.data.id,
518
- toPortId: toPort.id
519
- });
520
- }
521
- }
522
- });
523
-
524
- sortedNodes.forEach(node => {
525
- const nodeId = node.data.id;
526
- const parent = node.data.parent_path.split(' -> ')[0] || 'global';
527
- const role = node.data.type;
528
-
529
- if (parent !== 'global') {
530
- const parentNode = nodes.find(n => n.data.id === parent || n.data.label === parent.split('[')[0]);
531
- if (parentNode && parentNode.data.outputs.length > 0 && node.data.inputs.length > 0) {
532
- const fromPort = parentNode.data.outputs[0];
533
- const toPort = node.data.inputs[0];
534
- if (!connections.some(c => c.fromNodeId === parentNode.data.id && c.toNodeId === nodeId)) {
535
- createSplineConnection(parentNode, fromPort.id, node, toPort.id);
536
- connections.push({
537
- fromNodeId: parentNode.data.id,
538
- fromPortId: fromPort.id,
539
- toNodeId: nodeId,
540
- toPortId: toPort.id
541
- });
542
- }
543
- }
544
- }
545
-
546
- if (role.includes('variable')) {
547
- const varName = node.data.label;
548
- sortedNodes.forEach(otherNode => {
549
- if (otherNode !== node && otherNode.data.source.includes(varName)) {
550
- const fromPort = node.data.outputs[0];
551
- const toPort = otherNode.data.inputs[0];
552
- if (fromPort && toPort && !connections.some(c => c.fromNodeId === nodeId && c.toNodeId === otherNode.data.id)) {
553
- createSplineConnection(node, fromPort.id, otherNode, toPort.id);
554
- connections.push({
555
- fromNodeId: nodeId,
556
- fromPortId: fromPort.id,
557
- toNodeId: otherNode.data.id,
558
- toPortId: toPort.id
559
- });
560
- }
561
- }
562
- });
563
- }
564
- });
565
-
566
- layer.draw();
567
- saveNodes();
568
- }
569
-
570
- // Update full program
571
- function updateProgram() {
572
- const program = reconstructProgram();
573
- document.getElementById('codeInput').value = program;
574
- fetch('/update_program', {
575
  method: 'POST',
576
  headers: { 'Content-Type': 'application/json' },
577
- body: JSON.stringify({ code: program })
578
- })
579
- .then(response => response.json())
580
- .then(data => {
581
- if (data.error) {
582
- alert(data.error);
583
- } else {
584
- console.log('Program updated:', data);
585
- }
586
  })
587
- .catch(error => console.error('Error:', error));
 
588
  }
589
 
590
- // Reconstruct the original program
591
- function reconstructProgram() {
592
- const sortedNodes = [...nodes].sort((a, b) => {
593
- if (a.data.level !== b.data.level) return a.data.level - b.data.level;
594
- return a.data.y - b.data.y; // Sort by y within columns
595
- });
596
-
597
- let program = '';
598
- sortedNodes.forEach(node => {
599
- const source = node.data.source || '';
600
- const level = node.data.level || 0;
601
- const indent = ' '.repeat(level);
602
- program += indent + source.trim() + '\n';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
  });
604
 
605
- return program.trim();
606
- }
607
-
608
- // Add a manual node
609
- function addNode() {
610
- const node = createNode(
611
- 250, // Add to global column
612
- 50 + nodes.filter(n => n.data.parent_path === 'global').length * 80,
613
- 'Function',
614
- 'function',
615
- ['in1'],
616
- ['out1'],
617
- nodes.length,
618
- 'def new_function(): pass',
619
- 'global',
620
- 0
621
- );
622
- nodes.push(node);
623
- layer.add(node);
624
  layer.draw();
625
- saveNodes();
626
  }
627
 
628
- // Update spline connections when nodes move
629
- function createSplineConnection(fromNode, fromPortId, toNode, toPortId) {
630
- const fromPort = fromNode.data.outputs.find(p => p.id === fromPortId);
631
- const toPort = toNode.data.inputs.find(p => p.id === toPortId);
632
- if (!fromPort || !toPort) return;
633
-
634
- const startX = fromNode.x() + fromPort.circle.x();
635
- const startY = fromNode.y() + fromPort.circle.y();
636
- const endX = toNode.x() + toPort.circle.x();
637
- const endY = toNode.y() + toPort.circle.y();
638
-
639
- const control1X = startX + (endX - startX) / 3;
640
- const control1Y = startY;
641
- const control2X = startX + 2 * (endX - startX) / 3;
642
- const control2Y = endY;
643
-
644
- const spline = new Konva.Shape({
645
- sceneFunc: function(context, shape) {
646
- context.beginPath();
647
- context.moveTo(startX, startY);
648
- context.bezierCurveTo(control1X, control1Y, control2X, control2Y, endX, endY);
649
- context.fillStrokeShape(shape);
650
- },
651
- stroke: 'black',
652
- strokeWidth: 2
653
- });
654
-
655
- spline.data = {
656
- fromNodeId: fromNode.data.id,
657
- fromPortId: fromPortId,
658
- toNodeId: toNode.data.id,
659
- toPortId: toPortId
660
  };
661
- layer.add(spline);
662
- layer.draw();
663
- }
664
-
665
- // Update spline connections when nodes move
666
- function updateConnections() {
667
- layer.find('Shape').forEach(shape => {
668
- if (shape.data && shape.data.fromNodeId !== undefined) {
669
- const fromNode = nodes.find(n => n.data.id === shape.data.fromNodeId);
670
- const toNode = nodes.find(n => n.data.id === shape.data.toNodeId);
671
- if (fromNode && toNode) {
672
- const fromPort = fromNode.data.outputs.find(p => p.id === shape.data.fromPortId);
673
- const toPort = toNode.data.inputs.find(p => p.id === shape.data.toPortId);
674
- if (fromPort && toPort) {
675
- const startX = fromNode.x() + fromPort.circle.x();
676
- const startY = fromNode.y() + fromPort.circle.y();
677
- const endX = toNode.x() + toPort.circle.x();
678
- const endY = toNode.y() + toPort.circle.y();
679
 
680
- const control1X = startX + (endX - startX) / 3;
681
- const control1Y = startY;
682
- const control2X = startX + 2 * (endX - startX) / 3;
683
- const control2Y = endY;
684
-
685
- shape.sceneFunc(function(context, shape) {
686
- context.beginPath();
687
- context.moveTo(startX, startY);
688
- context.bezierCurveTo(control1X, control1Y, control2X, control2Y, endX, endY);
689
- context.fillStrokeShape(shape);
690
- });
691
- }
692
- }
693
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
694
  });
695
- layer.draw();
696
- }
697
 
698
- // Save nodes and connections to backend
699
- function saveNodes() {
700
- fetch('/save_nodes', {
701
- method: 'POST',
702
- headers: {
703
- 'Content-Type': 'application/json'
704
- },
705
- body: JSON.stringify({
706
- nodes: nodes.map(n => ({
707
- id: n.data.id,
708
- type: n.data.type,
709
- label: n.data.label,
710
- x: n.data.x,
711
- y: n.data.y,
712
- inputs: n.data.inputs.map(p => p.name),
713
- outputs: n.data.outputs.map(p => p.name),
714
- source: n.data.source,
715
- parent_path: n.data.parent_path,
716
- level: n.data.level
717
- })),
718
- connections: connections
719
- })
720
- })
721
- .then(response => response.json())
722
- .then(data => console.log('Saved:', data))
723
- .catch(error => console.error('Error:', error));
724
  }
725
 
726
- // Initial draw
727
- layer.draw();
 
 
 
 
1
  const stage = new Konva.Stage({
2
  container: 'container',
3
+ width: window.innerWidth - 350,
4
+ height: window.innerHeight,
5
  draggable: true
6
  });
7
 
8
  const layer = new Konva.Layer();
9
  stage.add(layer);
10
 
 
 
 
 
 
 
 
 
 
 
11
  let scale = 1;
 
 
 
 
12
  stage.on('wheel', (e) => {
13
  e.evt.preventDefault();
14
+ const oldScale = stage.scaleX();
15
  const pointer = stage.getPointerPosition();
16
+ const scaleBy = 1.1;
17
+ const newScale = e.evt.deltaY > 0 ? oldScale / scaleBy : oldScale * scaleBy;
18
+ stage.scale({ x: newScale, y: newScale });
 
 
 
 
 
 
19
  const newPos = {
20
+ x: pointer.x - (pointer.x - stage.x()) / oldScale * newScale,
21
+ y: pointer.y - (pointer.y - stage.y()) / oldScale * newScale
22
  };
23
  stage.position(newPos);
 
24
  });
25
 
26
+ function parseCode() {
27
+ const code = document.getElementById('codeInput').value;
28
+ fetch('/parse', {
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({ code: code })
32
  })
33
+ .then(res => res.json())
34
  .then(data => {
35
+ drawGraph(data);
36
+ document.getElementById('stats').innerHTML = `Nodes: ${data.nodes.length}<br>Generated Vectors: ${data.nodes.length}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
 
40
+ function exportDataset() {
41
+ const code = document.getElementById('codeInput').value;
42
+ fetch('/generate_dataset', {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  method: 'POST',
44
  headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify({ code: code })
 
 
 
 
 
 
 
 
46
  })
47
+ .then(res => res.json())
48
+ .then(data => alert(`Dataset entry created at: ${data.path}`));
49
  }
50
 
51
+ function drawGraph(data) {
52
+ layer.destroyChildren();
53
+
54
+ const nodeMap = {};
55
+ const startX = 50;
56
+ const startY = 50;
57
+
58
+ // Auto-Layout Logic: Indent based on 'level'
59
+ data.nodes.forEach((n, i) => {
60
+ const x = startX + (n.level * 60);
61
+ const y = startY + (i * 90);
62
+
63
+ const grp = createNodeGroup(x, y, n);
64
+ layer.add(grp);
65
+ nodeMap[n.id] = { grp, x, y, ...n };
66
+ });
67
+
68
+ // Draw Connections
69
+ data.connections.forEach(conn => {
70
+ const from = nodeMap[conn.from];
71
+ const to = nodeMap[conn.to];
72
+ if (from && to) {
73
+ const arrow = new Konva.Arrow({
74
+ points: [from.x + 100, from.y + 25, to.x, to.y + 25],
75
+ pointerLength: 6,
76
+ pointerWidth: 6,
77
+ fill: '#555',
78
+ stroke: '#555',
79
+ strokeWidth: 2,
80
+ tension: 0.4
81
+ });
82
+ layer.add(arrow);
83
+ arrow.moveToBottom();
84
+ }
85
  });
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  layer.draw();
 
88
  }
89
 
90
+ function createNodeGroup(x, y, data) {
91
+ const group = new Konva.Group({ x, y, draggable: true });
92
+
93
+ // Color mapping
94
+ const colors = {
95
+ 'function': '#C586C0', // Purple
96
+ 'if': '#CE9178', // Orange/Brown
97
+ 'for': '#CE9178',
98
+ 'assigned_variable': '#9CDCFE', // Blue
99
+ 'return': '#569CD6', // Dark Blue
100
+ 'other': '#4EC9B0' // Teal
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  };
102
+ const color = colors[data.type] || '#CCCCCC';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ const rect = new Konva.Rect({
105
+ width: 180,
106
+ height: 50,
107
+ fill: '#252526',
108
+ stroke: color,
109
+ strokeWidth: 2,
110
+ cornerRadius: 6,
111
+ shadowColor: 'black',
112
+ shadowBlur: 10,
113
+ shadowOpacity: 0.3
114
+ });
115
+
116
+ const label = new Konva.Text({
117
+ x: 10, y: 10,
118
+ text: data.label,
119
+ fontSize: 14,
120
+ fontFamily: 'JetBrains Mono',
121
+ fill: '#ffffff',
122
+ width: 160,
123
+ ellipsis: true
124
+ });
125
+
126
+ const subLabel = new Konva.Text({
127
+ x: 10, y: 30,
128
+ text: `Vec: [${data.vector[0]}, ${data.vector[1]}...]`,
129
+ fontSize: 10,
130
+ fontFamily: 'JetBrains Mono',
131
+ fill: '#888',
132
+ width: 160
133
+ });
134
+
135
+ group.add(rect);
136
+ group.add(label);
137
+ group.add(subLabel);
138
+
139
+ // Add tooltip on hover
140
+ group.on('mouseover', () => {
141
+ document.body.style.cursor = 'pointer';
142
+ rect.stroke('#fff');
143
+ layer.draw();
144
+ });
145
+
146
+ group.on('mouseout', () => {
147
+ document.body.style.cursor = 'default';
148
+ rect.stroke(color);
149
+ layer.draw();
150
  });
 
 
151
 
152
+ return group;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
154
 
155
+ window.addEventListener('resize', () => {
156
+ stage.width(window.innerWidth - 350);
157
+ stage.height(window.innerHeight);
158
+ });