ameerazam08 commited on
Commit
a895648
·
verified ·
1 Parent(s): fd832fc

Upload 22 files

Browse files
css/drag-drop-fix.css ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Enhanced dragging styles for the neural network playground */
2
+
3
+ /* Ensure canvas nodes have proper cursor styles */
4
+ .canvas-node {
5
+ cursor: grab !important;
6
+ /* Ensure proper z-indexing */
7
+ z-index: 10;
8
+ /* Smooth transitions for dragging effects */
9
+ transition: box-shadow 0.2s ease, transform 0.2s ease, z-index 0s;
10
+ }
11
+
12
+ /* Active dragging state */
13
+ .canvas-node.dragging {
14
+ cursor: grabbing !important;
15
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important;
16
+ /* Add a slight scale effect */
17
+ transform: scale(1.05) !important;
18
+ /* Make sure dragged node is on top */
19
+ z-index: 1000 !important;
20
+ /* Don't transition position while dragging */
21
+ transition: box-shadow 0.2s ease, transform 0.2s ease, z-index 0s !important;
22
+ }
23
+
24
+ /* Improve the connection lines */
25
+ .connection {
26
+ z-index: 5;
27
+ height: 3px;
28
+ /* Add glow effect */
29
+ box-shadow: 0 0 8px rgba(52, 152, 219, 0.5);
30
+ }
31
+
32
+ /* Make node controls more visible on hover */
33
+ .node-controls {
34
+ opacity: 0.6;
35
+ transition: opacity 0.2s ease;
36
+ }
37
+
38
+ .canvas-node:hover .node-controls {
39
+ opacity: 1;
40
+ }
41
+
42
+ /* Ensure node ports are visible and properly clickable */
43
+ .node-port {
44
+ cursor: crosshair;
45
+ z-index: 20;
46
+ width: 14px;
47
+ height: 14px;
48
+ }
49
+
50
+ /* Make the body grab cursor apply while dragging in case cursor leaves the element */
51
+ body.node-dragging {
52
+ cursor: grabbing !important;
53
+ }
54
+
55
+ /* Styles for the fixed drag-drop functionality */
56
+
57
+ /* Improve node dragging */
58
+ .node-dragging {
59
+ cursor: grabbing !important;
60
+ }
61
+
62
+ .canvas-node {
63
+ position: absolute;
64
+ z-index: 10;
65
+ transition: box-shadow 0.2s ease-in-out;
66
+ }
67
+
68
+ .canvas-node.dragging {
69
+ cursor: grabbing;
70
+ z-index: 1000 !important;
71
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
72
+ }
73
+
74
+ /* Connection styles */
75
+ .connection {
76
+ position: absolute;
77
+ height: 2px;
78
+ background-color: #3498db;
79
+ transform-origin: left center;
80
+ pointer-events: none;
81
+ z-index: 5;
82
+ }
83
+
84
+ .connection:after {
85
+ content: '';
86
+ position: absolute;
87
+ right: -5px;
88
+ top: -3px;
89
+ width: 8px;
90
+ height: 8px;
91
+ background-color: #3498db;
92
+ border-radius: 50%;
93
+ }
94
+
95
+ .temp-connection {
96
+ background-color: #95a5a6;
97
+ opacity: 0.7;
98
+ z-index: 4;
99
+ }
100
+
101
+ .temp-connection:after {
102
+ background-color: #95a5a6;
103
+ }
104
+
105
+ /* Improved port styles */
106
+ .node-port {
107
+ position: absolute;
108
+ width: 12px;
109
+ height: 12px;
110
+ background-color: #3498db;
111
+ border-radius: 50%;
112
+ z-index: 20;
113
+ border: 2px solid white;
114
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
115
+ cursor: pointer;
116
+ transition: transform 0.2s ease, background-color 0.2s ease;
117
+ }
118
+
119
+ .node-port:hover {
120
+ transform: scale(1.2);
121
+ background-color: #2980b9;
122
+ }
123
+
124
+ .port-in {
125
+ left: -6px;
126
+ top: 50%;
127
+ transform: translateY(-50%);
128
+ }
129
+
130
+ .port-out {
131
+ right: -6px;
132
+ top: 50%;
133
+ transform: translateY(-50%);
134
+ }
135
+
136
+ .port-in:hover, .port-out:hover {
137
+ transform: translateY(-50%) scale(1.2);
138
+ }
css/layer-editor.css ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Layer Editor Styles for Neural Network Playground */
2
+
3
+ /* Modal styling */
4
+ #layer-editor-modal {
5
+ display: none;
6
+ position: fixed;
7
+ z-index: 9999;
8
+ left: 0;
9
+ top: 0;
10
+ width: 100%;
11
+ height: 100%;
12
+ overflow: auto;
13
+ background-color: rgba(0, 0, 0, 0.5);
14
+ opacity: 0;
15
+ transition: opacity 0.3s ease;
16
+ }
17
+
18
+ #layer-editor-modal.active {
19
+ opacity: 1;
20
+ }
21
+
22
+ #layer-editor-modal .modal-content {
23
+ background-color: #fff;
24
+ margin: 10% auto;
25
+ padding: 25px;
26
+ border-radius: 8px;
27
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
28
+ width: 80%;
29
+ max-width: 600px;
30
+ position: relative;
31
+ transform: translateY(-20px);
32
+ transition: transform 0.3s ease;
33
+ overflow: hidden; /* Prevent content from causing layout shifts */
34
+ }
35
+
36
+ #layer-editor-modal.active .modal-content {
37
+ transform: translateY(0);
38
+ }
39
+
40
+ /* Make the modal more obvious for debugging */
41
+ #layer-editor-modal[data-visible="true"] {
42
+ /* Remove the debug border */
43
+ /* border: 3px solid red; */
44
+ }
45
+
46
+ .modal-title {
47
+ color: #2c3e50;
48
+ margin-top: 0;
49
+ margin-bottom: 20px;
50
+ padding-bottom: 10px;
51
+ border-bottom: 1px solid #eee;
52
+ }
53
+
54
+ /* Top close button */
55
+ .close-modal {
56
+ position: absolute;
57
+ right: 20px;
58
+ top: 15px;
59
+ font-size: 24px;
60
+ font-weight: bold;
61
+ cursor: pointer;
62
+ color: #aaa;
63
+ transition: color 0.2s ease;
64
+ z-index: 10; /* Ensure it's above other content */
65
+ }
66
+
67
+ .close-modal:hover {
68
+ color: #333;
69
+ }
70
+
71
+ /* Form styling */
72
+ .layer-form {
73
+ margin-bottom: 20px;
74
+ overflow: auto; /* Allow scrolling if form gets too long */
75
+ max-height: 60vh; /* Limit height to avoid modal being too tall */
76
+ }
77
+
78
+ .form-field {
79
+ margin-bottom: 15px;
80
+ }
81
+
82
+ .form-field label {
83
+ display: block;
84
+ font-weight: 600;
85
+ margin-bottom: 5px;
86
+ color: #2c3e50;
87
+ }
88
+
89
+ .form-field input,
90
+ .form-field select {
91
+ width: 100%;
92
+ padding: 8px 12px;
93
+ border: 1px solid #ddd;
94
+ border-radius: 4px;
95
+ font-size: 14px;
96
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
97
+ }
98
+
99
+ .form-field input:focus,
100
+ .form-field select:focus {
101
+ border-color: #3498db;
102
+ box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
103
+ outline: none;
104
+ }
105
+
106
+ .help-text {
107
+ display: block;
108
+ margin-top: 5px;
109
+ color: #7f8c8d;
110
+ font-size: 12px;
111
+ }
112
+
113
+ /* Button styling */
114
+ .modal-footer {
115
+ display: flex;
116
+ justify-content: flex-end;
117
+ padding-top: 15px;
118
+ border-top: 1px solid #eee;
119
+ background-color: #fff; /* Ensure background is solid */
120
+ position: relative; /* Establish a stacking context */
121
+ z-index: 5; /* Higher than the form */
122
+ }
123
+
124
+ .modal-footer button {
125
+ padding: 8px 16px;
126
+ margin-left: 10px;
127
+ border: none;
128
+ border-radius: 4px;
129
+ font-weight: 600;
130
+ cursor: pointer;
131
+ /* Remove transition to prevent flickering */
132
+ /* transition: background-color 0.2s ease; */
133
+ position: relative; /* Establish a stacking context */
134
+ z-index: 2; /* Higher than surrounding elements */
135
+ text-rendering: optimizeLegibility; /* Improve text rendering */
136
+ -webkit-font-smoothing: antialiased;
137
+ }
138
+
139
+ .save-layer-btn {
140
+ background-color: #3498db;
141
+ color: white;
142
+ /* Fix position and prevent movement */
143
+ transform: translateZ(0); /* Force GPU acceleration */
144
+ }
145
+
146
+ .save-layer-btn:hover {
147
+ background-color: #2980b9;
148
+ }
149
+
150
+ /* Override for close button in footer */
151
+ .modal-footer .close-modal {
152
+ position: static;
153
+ background-color: #e0e0e0;
154
+ color: #333;
155
+ font-size: 14px;
156
+ transform: translateZ(0); /* Force GPU acceleration */
157
+ }
158
+
159
+ .modal-footer .close-modal:hover {
160
+ background-color: #ccc;
161
+ }
162
+
163
+ /* For number inputs */
164
+ input[type="number"] {
165
+ -moz-appearance: textfield;
166
+ }
167
+
168
+ input[type="number"]::-webkit-outer-spin-button,
169
+ input[type="number"]::-webkit-inner-spin-button {
170
+ -webkit-appearance: none;
171
+ margin: 0;
172
+ }
173
+
174
+ /* Error state */
175
+ .form-field.error input,
176
+ .form-field.error select {
177
+ border-color: #e74c3c;
178
+ }
179
+
180
+ .error-message {
181
+ color: #e74c3c;
182
+ font-size: 12px;
183
+ margin-top: 5px;
184
+ }
185
+
186
+ /* Responsive adjustments */
187
+ @media (max-width: 768px) {
188
+ #layer-editor-modal .modal-content {
189
+ width: 90%;
190
+ margin: 15% auto;
191
+ padding: 15px;
192
+ }
193
+
194
+ .modal-footer {
195
+ flex-direction: column;
196
+ }
197
+
198
+ .modal-footer button {
199
+ margin-left: 0;
200
+ margin-top: 10px;
201
+ }
202
+ }
css/styles.css CHANGED
@@ -876,27 +876,43 @@ select {
876
  }
877
 
878
  .node-controls {
 
 
 
879
  display: flex;
880
  justify-content: flex-end;
881
- margin-top: 0.5rem;
882
  }
883
 
884
  .node-edit-btn, .node-delete-btn {
885
- background: none;
886
- border: none;
887
  cursor: pointer;
888
- padding: 3px;
889
  border-radius: 4px;
890
  transition: all 0.2s ease;
891
  margin-left: 0.3rem;
 
 
 
 
 
 
 
892
  }
893
 
894
  .node-edit-btn:hover {
895
  background: #e0e0e0;
896
  }
897
 
 
 
 
 
 
898
  .node-delete-btn:hover {
899
  background: #ffcdd2;
 
900
  }
901
 
902
  .icon {
@@ -1051,6 +1067,41 @@ select {
1051
  display: grid;
1052
  grid-template-columns: 1fr 1fr;
1053
  gap: 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1054
  }
1055
 
1056
  .form-group {
@@ -1330,4 +1381,661 @@ select {
1330
  stroke-dasharray: 5, 5;
1331
  stroke-width: 2;
1332
  fill: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1333
  }
 
876
  }
877
 
878
  .node-controls {
879
+ position: absolute;
880
+ top: 4px;
881
+ right: 4px;
882
  display: flex;
883
  justify-content: flex-end;
884
+ z-index: 2;
885
  }
886
 
887
  .node-edit-btn, .node-delete-btn {
888
+ background: rgba(255, 255, 255, 0.7);
889
+ border: 1px solid rgba(0, 0, 0, 0.1);
890
  cursor: pointer;
891
+ padding: 2px 4px;
892
  border-radius: 4px;
893
  transition: all 0.2s ease;
894
  margin-left: 0.3rem;
895
+ font-size: 14px;
896
+ line-height: 1;
897
+ display: flex;
898
+ align-items: center;
899
+ justify-content: center;
900
+ width: 20px;
901
+ height: 20px;
902
  }
903
 
904
  .node-edit-btn:hover {
905
  background: #e0e0e0;
906
  }
907
 
908
+ .node-delete-btn {
909
+ color: #b71c1c;
910
+ font-weight: bold;
911
+ }
912
+
913
  .node-delete-btn:hover {
914
  background: #ffcdd2;
915
+ color: #d50000;
916
  }
917
 
918
  .icon {
 
1067
  display: grid;
1068
  grid-template-columns: 1fr 1fr;
1069
  gap: 1rem;
1070
+ margin-bottom: 20px;
1071
+ }
1072
+
1073
+ /* Modal footer styling */
1074
+ .modal-footer {
1075
+ display: flex;
1076
+ justify-content: flex-end;
1077
+ gap: 10px;
1078
+ margin-top: 20px;
1079
+ padding-top: 15px;
1080
+ border-top: 1px solid var(--border-color);
1081
+ }
1082
+
1083
+ .modal-footer button {
1084
+ padding: 8px 16px;
1085
+ border-radius: var(--border-radius);
1086
+ border: none;
1087
+ cursor: pointer;
1088
+ font-size: 14px;
1089
+ }
1090
+
1091
+ .modal-footer .save-layer-btn {
1092
+ background-color: var(--primary-color);
1093
+ color: white;
1094
+ }
1095
+
1096
+ .modal-footer .close-modal {
1097
+ background-color: #f1f1f1;
1098
+ color: #333;
1099
+ position: static;
1100
+ font-size: 14px;
1101
+ }
1102
+
1103
+ .modal-footer .close-modal:hover {
1104
+ background-color: #e0e0e0;
1105
  }
1106
 
1107
  .form-group {
 
1381
  stroke-dasharray: 5, 5;
1382
  stroke-width: 2;
1383
  fill: none;
1384
+ }
1385
+
1386
+ /* Tab Navigation */
1387
+ .tabs-container {
1388
+ margin-top: 20px;
1389
+ width: 100%;
1390
+ display: flex;
1391
+ justify-content: center;
1392
+ }
1393
+
1394
+ .tab-nav {
1395
+ display: flex;
1396
+ gap: 10px;
1397
+ background-color: #f5f5f7;
1398
+ padding: 10px;
1399
+ border-radius: 8px;
1400
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
1401
+ }
1402
+
1403
+ .tab-button {
1404
+ padding: 10px 20px;
1405
+ background-color: transparent;
1406
+ border: none;
1407
+ border-radius: 6px;
1408
+ font-size: 16px;
1409
+ font-weight: 500;
1410
+ color: #555;
1411
+ cursor: pointer;
1412
+ transition: all 0.2s ease;
1413
+ }
1414
+
1415
+ .tab-button:hover {
1416
+ background-color: rgba(0,0,0,0.05);
1417
+ }
1418
+
1419
+ .tab-button.active {
1420
+ background-color: #3498db;
1421
+ color: white;
1422
+ }
1423
+
1424
+ .tab-content {
1425
+ display: none;
1426
+ width: 100%;
1427
+ }
1428
+
1429
+ .tab-content.active {
1430
+ display: block;
1431
+ }
1432
+
1433
+ /* Backpropagation Tab Styles */
1434
+ .backprop-container {
1435
+ display: grid;
1436
+ grid-template-columns: 280px 1fr 280px;
1437
+ gap: 20px;
1438
+ margin: 20px auto;
1439
+ max-width: 1600px;
1440
+ }
1441
+
1442
+ .backprop-info-panel {
1443
+ background-color: white;
1444
+ border-radius: 8px;
1445
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1446
+ padding: 20px;
1447
+ }
1448
+
1449
+ .intro-text {
1450
+ line-height: 1.6;
1451
+ margin-bottom: 20px;
1452
+ color: #555;
1453
+ }
1454
+
1455
+ .backprop-steps {
1456
+ margin-bottom: 20px;
1457
+ }
1458
+
1459
+ .backprop-steps ol {
1460
+ padding-left: 25px;
1461
+ }
1462
+
1463
+ .backprop-steps li {
1464
+ margin-bottom: 15px;
1465
+ }
1466
+
1467
+ .backprop-steps strong {
1468
+ color: #333;
1469
+ display: block;
1470
+ margin-bottom: 5px;
1471
+ }
1472
+
1473
+ .backprop-steps p {
1474
+ color: #666;
1475
+ margin: 0;
1476
+ font-size: 14px;
1477
+ }
1478
+
1479
+ .backprop-controls {
1480
+ margin-top: 30px;
1481
+ display: flex;
1482
+ flex-direction: column;
1483
+ gap: 10px;
1484
+ }
1485
+
1486
+ .backprop-controls button {
1487
+ padding: 10px 15px;
1488
+ border: none;
1489
+ border-radius: 6px;
1490
+ font-weight: 500;
1491
+ cursor: pointer;
1492
+ transition: all 0.2s ease;
1493
+ }
1494
+
1495
+ #start-animation {
1496
+ background-color: #2ecc71;
1497
+ color: white;
1498
+ }
1499
+
1500
+ #pause-animation {
1501
+ background-color: #f39c12;
1502
+ color: white;
1503
+ }
1504
+
1505
+ #reset-animation {
1506
+ background-color: #e74c3c;
1507
+ color: white;
1508
+ }
1509
+
1510
+ .backprop-controls button:hover {
1511
+ opacity: 0.9;
1512
+ transform: translateY(-2px);
1513
+ }
1514
+
1515
+ .speed-control {
1516
+ display: flex;
1517
+ align-items: center;
1518
+ gap: 10px;
1519
+ margin-top: 10px;
1520
+ }
1521
+
1522
+ .speed-control label {
1523
+ font-size: 14px;
1524
+ color: #666;
1525
+ }
1526
+
1527
+ .speed-control input {
1528
+ flex: 1;
1529
+ }
1530
+
1531
+ .backprop-visualization {
1532
+ background-color: white;
1533
+ border-radius: 8px;
1534
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1535
+ padding: 20px;
1536
+ display: flex;
1537
+ flex-direction: column;
1538
+ gap: 20px;
1539
+ }
1540
+
1541
+ .animation-container {
1542
+ position: relative;
1543
+ min-height: 400px;
1544
+ background-color: #f8f9fa;
1545
+ border-radius: 8px;
1546
+ overflow: hidden;
1547
+ }
1548
+
1549
+ #backprop-canvas {
1550
+ width: 100%;
1551
+ height: 100%;
1552
+ position: absolute;
1553
+ top: 0;
1554
+ left: 0;
1555
+ }
1556
+
1557
+ .animation-overlay {
1558
+ position: absolute;
1559
+ bottom: 20px;
1560
+ left: 20px;
1561
+ right: 20px;
1562
+ background-color: rgba(255, 255, 255, 0.9);
1563
+ padding: 15px;
1564
+ border-radius: 6px;
1565
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
1566
+ }
1567
+
1568
+ #current-step-info h4 {
1569
+ margin: 0 0 10px 0;
1570
+ color: #333;
1571
+ }
1572
+
1573
+ #step-name {
1574
+ color: #3498db;
1575
+ font-weight: 600;
1576
+ }
1577
+
1578
+ #step-description {
1579
+ margin: 0;
1580
+ color: #555;
1581
+ font-size: 14px;
1582
+ }
1583
+
1584
+ .formula-section {
1585
+ padding: 15px;
1586
+ background-color: #f8f9fa;
1587
+ border-radius: 8px;
1588
+ }
1589
+
1590
+ .formula-display {
1591
+ margin: 15px 0;
1592
+ display: flex;
1593
+ flex-direction: column;
1594
+ gap: 10px;
1595
+ }
1596
+
1597
+ .formula {
1598
+ display: flex;
1599
+ flex-direction: column;
1600
+ padding: 10px;
1601
+ background-color: #fff;
1602
+ border-radius: 6px;
1603
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
1604
+ }
1605
+
1606
+ .formula-title {
1607
+ font-size: 14px;
1608
+ color: #666;
1609
+ margin-bottom: 5px;
1610
+ }
1611
+
1612
+ .formula-math {
1613
+ font-family: 'Times New Roman', serif;
1614
+ font-size: 20px;
1615
+ color: #333;
1616
+ padding: 5px 0;
1617
+ }
1618
+
1619
+ .formula-explanation {
1620
+ font-size: 14px;
1621
+ color: #666;
1622
+ line-height: 1.5;
1623
+ }
1624
+
1625
+ .backprop-details-panel {
1626
+ background-color: white;
1627
+ border-radius: 8px;
1628
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1629
+ padding: 20px;
1630
+ }
1631
+
1632
+ .animation-legend {
1633
+ margin: 15px 0 25px;
1634
+ }
1635
+
1636
+ .legend-item {
1637
+ display: flex;
1638
+ align-items: center;
1639
+ margin-bottom: 10px;
1640
+ }
1641
+
1642
+ .color-box {
1643
+ width: 20px;
1644
+ height: 20px;
1645
+ border-radius: 4px;
1646
+ margin-right: 10px;
1647
+ }
1648
+
1649
+ .forward-color {
1650
+ background-color: #3498db;
1651
+ }
1652
+
1653
+ .error-color {
1654
+ background-color: #e74c3c;
1655
+ }
1656
+
1657
+ .backward-color {
1658
+ background-color: #9b59b6;
1659
+ }
1660
+
1661
+ .update-color {
1662
+ background-color: #2ecc71;
1663
+ }
1664
+
1665
+ .variable-display {
1666
+ margin-top: 20px;
1667
+ }
1668
+
1669
+ .variable-display h4 {
1670
+ margin-bottom: 15px;
1671
+ color: #333;
1672
+ }
1673
+
1674
+ #variables-container {
1675
+ background-color: #f8f9fa;
1676
+ padding: 15px;
1677
+ border-radius: 6px;
1678
+ font-family: 'Courier New', monospace;
1679
+ font-size: 14px;
1680
+ min-height: 150px;
1681
+ }
1682
+
1683
+ /* Responsive styles for backpropagation tab */
1684
+ @media (max-width: 1200px) {
1685
+ .backprop-container {
1686
+ grid-template-columns: 1fr;
1687
+ grid-template-rows: auto auto auto;
1688
+ }
1689
+
1690
+ .animation-container {
1691
+ min-height: 300px;
1692
+ }
1693
+ }
1694
+
1695
+ /* Forward Propagation Tab Styles */
1696
+ .forward-container {
1697
+ display: grid;
1698
+ grid-template-columns: 280px 1fr 280px;
1699
+ gap: 20px;
1700
+ margin: 20px auto;
1701
+ max-width: 1600px;
1702
+ }
1703
+
1704
+ .forward-info-panel {
1705
+ background-color: white;
1706
+ border-radius: 8px;
1707
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1708
+ padding: 20px;
1709
+ }
1710
+
1711
+ .forward-steps {
1712
+ margin-bottom: 20px;
1713
+ }
1714
+
1715
+ .forward-steps ol {
1716
+ padding-left: 25px;
1717
+ }
1718
+
1719
+ .forward-steps li {
1720
+ margin-bottom: 15px;
1721
+ }
1722
+
1723
+ .forward-steps strong {
1724
+ color: #333;
1725
+ display: block;
1726
+ margin-bottom: 5px;
1727
+ }
1728
+
1729
+ .forward-steps p {
1730
+ color: #666;
1731
+ margin: 0;
1732
+ font-size: 14px;
1733
+ }
1734
+
1735
+ .forward-controls {
1736
+ margin-top: 30px;
1737
+ display: flex;
1738
+ flex-direction: column;
1739
+ gap: 10px;
1740
+ }
1741
+
1742
+ .forward-controls button {
1743
+ padding: 10px 15px;
1744
+ border: none;
1745
+ border-radius: 6px;
1746
+ font-weight: 500;
1747
+ cursor: pointer;
1748
+ transition: all 0.2s ease;
1749
+ }
1750
+
1751
+ #start-forward-animation {
1752
+ background-color: #2ecc71;
1753
+ color: white;
1754
+ }
1755
+
1756
+ #pause-forward-animation {
1757
+ background-color: #f39c12;
1758
+ color: white;
1759
+ }
1760
+
1761
+ #reset-forward-animation {
1762
+ background-color: #e74c3c;
1763
+ color: white;
1764
+ }
1765
+
1766
+ .data-input-control {
1767
+ margin-top: 20px;
1768
+ display: flex;
1769
+ flex-direction: column;
1770
+ gap: 8px;
1771
+ }
1772
+
1773
+ .data-input-control label {
1774
+ font-size: 14px;
1775
+ color: #666;
1776
+ }
1777
+
1778
+ .data-input-control select {
1779
+ padding: 8px;
1780
+ border-radius: 6px;
1781
+ border: 1px solid #ddd;
1782
+ }
1783
+
1784
+ .forward-visualization {
1785
+ background-color: white;
1786
+ border-radius: 8px;
1787
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1788
+ padding: 20px;
1789
+ display: flex;
1790
+ flex-direction: column;
1791
+ gap: 20px;
1792
+ }
1793
+
1794
+ .computation-section {
1795
+ background-color: #f8f9fa;
1796
+ border-radius: 8px;
1797
+ padding: 15px;
1798
+ }
1799
+
1800
+ .computation-display {
1801
+ background-color: white;
1802
+ border-radius: 6px;
1803
+ padding: 10px;
1804
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
1805
+ margin: 15px 0;
1806
+ }
1807
+
1808
+ #computation-values {
1809
+ font-family: 'Courier New', monospace;
1810
+ background-color: #f8f9fa;
1811
+ border-radius: 6px;
1812
+ padding: 10px;
1813
+ max-height: 150px;
1814
+ overflow-y: auto;
1815
+ font-size: 14px;
1816
+ }
1817
+
1818
+ .forward-details-panel {
1819
+ background-color: white;
1820
+ border-radius: 8px;
1821
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1822
+ padding: 20px;
1823
+ }
1824
+
1825
+ .network-architecture {
1826
+ margin-bottom: 20px;
1827
+ }
1828
+
1829
+ .architecture-info {
1830
+ background-color: #f8f9fa;
1831
+ border-radius: 6px;
1832
+ padding: 12px;
1833
+ margin-top: 10px;
1834
+ }
1835
+
1836
+ .architecture-info p {
1837
+ margin: 5px 0;
1838
+ font-size: 14px;
1839
+ }
1840
+
1841
+ .activation-functions {
1842
+ margin-top: 20px;
1843
+ }
1844
+
1845
+ .function-item {
1846
+ background-color: #f8f9fa;
1847
+ border-radius: 6px;
1848
+ padding: 10px;
1849
+ margin-top: 10px;
1850
+ display: flex;
1851
+ justify-content: space-between;
1852
+ align-items: center;
1853
+ }
1854
+
1855
+ .function-name {
1856
+ font-weight: 500;
1857
+ font-size: 14px;
1858
+ }
1859
+
1860
+ .function-formula {
1861
+ font-family: 'Times New Roman', serif;
1862
+ font-size: 16px;
1863
+ }
1864
+
1865
+ /* Background Animation Tab Styles */
1866
+ .background-animation-container {
1867
+ display: grid;
1868
+ grid-template-columns: 280px 1fr 280px;
1869
+ gap: 20px;
1870
+ margin: 20px auto;
1871
+ max-width: 1600px;
1872
+ }
1873
+
1874
+ .background-info-panel {
1875
+ background-color: white;
1876
+ border-radius: 8px;
1877
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1878
+ padding: 20px;
1879
+ }
1880
+
1881
+ .visualization-controls {
1882
+ margin-top: 20px;
1883
+ }
1884
+
1885
+ .control-group {
1886
+ margin-bottom: 20px;
1887
+ }
1888
+
1889
+ .control-item {
1890
+ margin: 15px 0;
1891
+ display: flex;
1892
+ flex-direction: column;
1893
+ gap: 8px;
1894
+ }
1895
+
1896
+ .control-item label {
1897
+ font-size: 14px;
1898
+ color: #666;
1899
+ }
1900
+
1901
+ .control-item input[type="range"] {
1902
+ width: 100%;
1903
+ }
1904
+
1905
+ .control-item select {
1906
+ padding: 8px;
1907
+ border-radius: 6px;
1908
+ border: 1px solid #ddd;
1909
+ }
1910
+
1911
+ .animation-buttons {
1912
+ display: flex;
1913
+ flex-direction: column;
1914
+ gap: 10px;
1915
+ margin-top: 20px;
1916
+ }
1917
+
1918
+ .animation-buttons button {
1919
+ padding: 10px 15px;
1920
+ border: none;
1921
+ border-radius: 6px;
1922
+ font-weight: 500;
1923
+ cursor: pointer;
1924
+ transition: all 0.2s ease;
1925
+ }
1926
+
1927
+ #start-background-animation {
1928
+ background-color: #2ecc71;
1929
+ color: white;
1930
+ }
1931
+
1932
+ #pause-background-animation {
1933
+ background-color: #f39c12;
1934
+ color: white;
1935
+ }
1936
+
1937
+ #reset-background-animation {
1938
+ background-color: #e74c3c;
1939
+ color: white;
1940
+ }
1941
+
1942
+ .background-visualization {
1943
+ background-color: white;
1944
+ border-radius: 8px;
1945
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1946
+ padding: 20px;
1947
+ position: relative;
1948
+ min-height: 500px;
1949
+ }
1950
+
1951
+ #background-canvas {
1952
+ position: absolute;
1953
+ top: 0;
1954
+ left: 0;
1955
+ width: 100%;
1956
+ height: 100%;
1957
+ border-radius: 8px;
1958
+ background-color: #f8f9fa;
1959
+ }
1960
+
1961
+ .background-details-panel {
1962
+ background-color: white;
1963
+ border-radius: 8px;
1964
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
1965
+ padding: 20px;
1966
+ }
1967
+
1968
+ .background-details-panel p {
1969
+ margin-bottom: 15px;
1970
+ color: #555;
1971
+ font-size: 14px;
1972
+ line-height: 1.6;
1973
+ }
1974
+
1975
+ .stats-panel {
1976
+ margin-top: 20px;
1977
+ background-color: #f8f9fa;
1978
+ border-radius: 8px;
1979
+ padding: 15px;
1980
+ }
1981
+
1982
+ .stats-panel h4 {
1983
+ margin-bottom: 10px;
1984
+ color: #333;
1985
+ }
1986
+
1987
+ #stats-container {
1988
+ display: grid;
1989
+ grid-template-columns: repeat(3, 1fr);
1990
+ gap: 10px;
1991
+ text-align: center;
1992
+ }
1993
+
1994
+ .stat-item {
1995
+ background-color: white;
1996
+ border-radius: 6px;
1997
+ padding: 10px;
1998
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
1999
+ }
2000
+
2001
+ .stat-label {
2002
+ font-size: 12px;
2003
+ color: #666;
2004
+ margin-bottom: 5px;
2005
+ }
2006
+
2007
+ .stat-value {
2008
+ font-size: 16px;
2009
+ font-weight: 600;
2010
+ color: #3498db;
2011
+ }
2012
+
2013
+ /* Neuron animations */
2014
+ @keyframes neuronPulse {
2015
+ 0% { transform: scale(1); opacity: 0.7; }
2016
+ 50% { transform: scale(1.2); opacity: 1; }
2017
+ 100% { transform: scale(1); opacity: 0.7; }
2018
+ }
2019
+
2020
+ @keyframes connectionPulse {
2021
+ 0% { opacity: 0.2; }
2022
+ 50% { opacity: 0.8; }
2023
+ 100% { opacity: 0.2; }
2024
+ }
2025
+
2026
+ /* Responsive styles for new tabs */
2027
+ @media (max-width: 1200px) {
2028
+ .forward-container,
2029
+ .background-animation-container {
2030
+ grid-template-columns: 1fr;
2031
+ grid-template-rows: auto auto auto;
2032
+ }
2033
+
2034
+ .background-visualization {
2035
+ min-height: 400px;
2036
+ }
2037
+
2038
+ #stats-container {
2039
+ grid-template-columns: 1fr;
2040
+ }
2041
  }
index.html CHANGED
@@ -5,6 +5,8 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Neural Network Playground</title>
7
  <link rel="stylesheet" href="css/styles.css">
 
 
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
@@ -14,105 +16,408 @@
14
  <header>
15
  <h1>Neural Network Playground</h1>
16
  <p class="header-subtitle">Interactive visualization of neural network architectures and concepts</p>
 
 
 
 
 
 
 
 
 
 
17
  </header>
18
 
19
  <main>
20
- <div class="container">
21
- <div class="tools-panel">
22
- <h2>Network Components</h2>
23
-
24
- <p class="hint-text">Drag components to the canvas to build your neural network</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- <div class="node-types">
27
- <div class="node-item" draggable="true" data-type="input">
28
- <div class="node input-node">Input Layer</div>
 
 
 
 
29
  </div>
30
- <div class="node-item" draggable="true" data-type="hidden">
31
- <div class="node hidden-node">Hidden Layer</div>
 
 
 
 
32
  </div>
33
- <div class="node-item" draggable="true" data-type="output">
34
- <div class="node output-node">Output Layer</div>
 
 
 
 
35
  </div>
36
- <div class="node-item" draggable="true" data-type="conv">
37
- <div class="node conv-node">Convolutional</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  </div>
39
- <div class="node-item" draggable="true" data-type="pool">
40
- <div class="node pool-node">Pooling</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  </div>
42
- <div class="node-item" draggable="true" data-type="linear">
43
- <div class="node linear-node">Linear Regression</div>
 
 
 
 
 
 
 
44
  </div>
45
  </div>
46
 
47
- <h3 class="section-title">Sample Data</h3>
48
- <div class="sample-data">
49
- <div class="sample-item" data-sample="1">5</div>
50
- <div class="sample-item" data-sample="2">7</div>
51
- <div class="sample-item" data-sample="3">3</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  </div>
53
 
54
- <div class="controls">
55
- <button id="run-network">Run Network</button>
56
- <button id="clear-canvas">Clear Canvas</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  </div>
58
 
59
- <h3 class="section-title">Network Settings</h3>
60
- <div class="network-settings">
61
- <div class="setting-group">
62
- <label for="learning-rate">Learning Rate:</label>
63
- <input type="range" id="learning-rate" min="0.001" max="1" step="0.001" value="0.1">
64
- <span class="setting-value" id="learning-rate-value">0.1</span>
65
- </div>
66
- <div class="setting-group">
67
- <label for="activation">Activation:</label>
68
- <select id="activation">
69
- <option value="relu">ReLU</option>
70
- <option value="sigmoid">Sigmoid</option>
71
- <option value="tanh">Tanh</option>
72
- </select>
 
 
 
 
 
 
 
 
 
 
73
  </div>
74
  </div>
75
- </div>
76
-
77
- <div class="canvas-container">
78
- <div id="network-canvas" class="network-canvas">
79
- <div class="canvas-hint">
80
- <strong>Build Your Neural Network</strong>
81
- Drag components from the left panel and drop them here.
82
- <br>Connect them by dragging from output (right) to input (left) ports.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  </div>
84
  </div>
85
  </div>
86
-
87
- <div class="properties-panel">
88
- <h2>Layer Properties</h2>
89
- <div id="node-properties">
90
- <p>Hover over a node to see its properties</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  </div>
92
 
93
- <h3 class="section-title">Activation Function</h3>
94
- <div class="activation-graph">
95
- <svg class="activation-curve" viewBox="0 0 100 100" preserveAspectRatio="none">
96
- <!-- Will be populated by JavaScript -->
97
- </svg>
98
  </div>
99
 
100
- <h3 class="section-title">Layer Weights</h3>
101
- <div id="weight-visualization"></div>
102
-
103
- <h3 class="section-title">Training Progress</h3>
104
- <div class="training-progress">
105
- <div class="progress-bar-container">
106
- <div class="progress-bar" style="width: 0%"></div>
107
- </div>
108
- <div class="metrics">
109
- <div class="metric">
110
- <span class="metric-label">Loss:</span>
111
- <span class="metric-value" id="loss-value">-</span>
112
- </div>
113
- <div class="metric">
114
- <span class="metric-label">Accuracy:</span>
115
- <span class="metric-value" id="accuracy-value">-</span>
 
 
 
 
 
 
 
 
 
 
 
116
  </div>
117
  </div>
118
  </div>
@@ -155,19 +460,34 @@
155
  </div>
156
 
157
  <!-- Layer Editor Modal -->
158
- <div id="layer-editor-modal" class="modal layer-editor-modal">
159
  <div class="modal-content">
160
  <span class="close-modal">&times;</span>
161
  <h2 class="modal-title">Edit Layer</h2>
162
  <form class="layer-form">
163
  <!-- Form fields will be dynamically generated based on layer type -->
164
  </form>
 
 
 
 
165
  </div>
166
  </div>
167
  <!-- End Layer Editor Modal -->
168
 
169
- <script src="js/main.js"></script>
 
 
170
  <script src="js/neural-network.js"></script>
171
- <script src="js/drag-drop.js"></script>
 
 
 
 
 
 
 
 
 
172
  </body>
173
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Neural Network Playground</title>
7
  <link rel="stylesheet" href="css/styles.css">
8
+ <link rel="stylesheet" href="css/drag-drop-fix.css">
9
+ <link rel="stylesheet" href="css/layer-editor.css">
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
 
16
  <header>
17
  <h1>Neural Network Playground</h1>
18
  <p class="header-subtitle">Interactive visualization of neural network architectures and concepts</p>
19
+
20
+ <!-- Tab Navigation -->
21
+ <div class="tabs-container">
22
+ <div class="tab-nav">
23
+ <button class="tab-button active" data-tab="network-design">Network Design</button>
24
+ <button class="tab-button" data-tab="backpropagation">Backpropagation</button>
25
+ <button class="tab-button" data-tab="forward-propagation">Forward Propagation</button>
26
+ <button class="tab-button" data-tab="background-animation">Neural Background</button>
27
+ </div>
28
+ </div>
29
  </header>
30
 
31
  <main>
32
+ <!-- Network Design Tab -->
33
+ <div class="tab-content active" id="network-design-tab">
34
+ <div class="container">
35
+ <div class="tools-panel">
36
+ <h2>Network Components</h2>
37
+
38
+ <p class="hint-text">Drag components to the canvas to build your neural network</p>
39
+
40
+ <div class="node-types">
41
+ <div class="node-item" draggable="true" data-type="input">
42
+ <div class="node input-node">Input Layer</div>
43
+ </div>
44
+ <div class="node-item" draggable="true" data-type="hidden">
45
+ <div class="node hidden-node">Hidden Layer</div>
46
+ </div>
47
+ <div class="node-item" draggable="true" data-type="output">
48
+ <div class="node output-node">Output Layer</div>
49
+ </div>
50
+ <div class="node-item" draggable="true" data-type="conv">
51
+ <div class="node conv-node">Convolutional</div>
52
+ </div>
53
+ <div class="node-item" draggable="true" data-type="pool">
54
+ <div class="node pool-node">Pooling</div>
55
+ </div>
56
+ <div class="node-item" draggable="true" data-type="linear">
57
+ <div class="node linear-node">Linear Regression</div>
58
+ </div>
59
+ </div>
60
+
61
+ <h3 class="section-title">Sample Data</h3>
62
+ <div class="sample-data">
63
+ <div class="sample-item" data-sample="1">5</div>
64
+ <div class="sample-item" data-sample="2">7</div>
65
+ <div class="sample-item" data-sample="3">3</div>
66
+ </div>
67
+
68
+ <div class="controls">
69
+ <button id="run-network">Run Network</button>
70
+ <button id="clear-canvas">Clear Canvas</button>
71
+ </div>
72
+
73
+ <h3 class="section-title">Network Settings</h3>
74
+ <div class="network-settings">
75
+ <div class="setting-group">
76
+ <label for="learning-rate">Learning Rate:</label>
77
+ <input type="range" id="learning-rate" min="0.001" max="1" step="0.001" value="0.1">
78
+ <span class="setting-value" id="learning-rate-value">0.1</span>
79
+ </div>
80
+ <div class="setting-group">
81
+ <label for="activation">Activation:</label>
82
+ <select id="activation">
83
+ <option value="relu">ReLU</option>
84
+ <option value="sigmoid">Sigmoid</option>
85
+ <option value="tanh">Tanh</option>
86
+ </select>
87
+ </div>
88
+ </div>
89
+ </div>
90
 
91
+ <div class="canvas-container">
92
+ <div id="network-canvas" class="network-canvas">
93
+ <div class="canvas-hint">
94
+ <strong>Build Your Neural Network</strong>
95
+ Drag components from the left panel and drop them here.
96
+ <br>Connect them by dragging from output (right) to input (left) ports.
97
+ </div>
98
  </div>
99
+ </div>
100
+
101
+ <div class="properties-panel">
102
+ <h2>Layer Properties</h2>
103
+ <div id="node-properties">
104
+ <p>Hover over a node to see its properties</p>
105
  </div>
106
+
107
+ <h3 class="section-title">Activation Function</h3>
108
+ <div class="activation-graph">
109
+ <svg class="activation-curve" viewBox="0 0 100 100" preserveAspectRatio="none">
110
+ <!-- Will be populated by JavaScript -->
111
+ </svg>
112
  </div>
113
+
114
+ <h3 class="section-title">Layer Weights</h3>
115
+ <div id="weight-visualization"></div>
116
+
117
+ <h3 class="section-title">Training Progress</h3>
118
+ <div class="training-progress">
119
+ <div class="progress-bar-container">
120
+ <div class="progress-bar" style="width: 0%"></div>
121
+ </div>
122
+ <div class="metrics">
123
+ <div class="metric">
124
+ <span class="metric-label">Loss:</span>
125
+ <span class="metric-value" id="loss-value">-</span>
126
+ </div>
127
+ <div class="metric">
128
+ <span class="metric-label">Accuracy:</span>
129
+ <span class="metric-value" id="accuracy-value">-</span>
130
+ </div>
131
+ </div>
132
  </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Backpropagation Tab -->
138
+ <div class="tab-content" id="backpropagation-tab">
139
+ <div class="container backprop-container">
140
+ <div class="backprop-info-panel">
141
+ <h2>Backpropagation Explained</h2>
142
+ <p class="intro-text">
143
+ Backpropagation is the key algorithm that allows neural networks to learn from data. It works by
144
+ calculating how much each weight in the network contributes to the overall error and adjusting
145
+ the weights to minimize this error.
146
+ </p>
147
+
148
+ <div class="backprop-steps">
149
+ <h3>The Steps of Backpropagation:</h3>
150
+ <ol>
151
+ <li>
152
+ <strong>Forward Pass</strong>
153
+ <p>Input data flows through the network to produce a prediction.</p>
154
+ </li>
155
+ <li>
156
+ <strong>Calculate Error</strong>
157
+ <p>Compare the prediction with the expected output to compute the error.</p>
158
+ </li>
159
+ <li>
160
+ <strong>Backward Pass</strong>
161
+ <p>Propagate the error backward through the network.</p>
162
+ </li>
163
+ <li>
164
+ <strong>Update Weights</strong>
165
+ <p>Adjust each weight based on its contribution to the error.</p>
166
+ </li>
167
+ </ol>
168
  </div>
169
+
170
+ <div class="backprop-controls">
171
+ <button id="start-animation">Start Animation</button>
172
+ <button id="pause-animation">Pause</button>
173
+ <button id="reset-animation">Reset</button>
174
+ <div class="speed-control">
175
+ <label for="animation-speed">Speed:</label>
176
+ <input type="range" id="animation-speed" min="1" max="10" value="5">
177
+ </div>
178
  </div>
179
  </div>
180
 
181
+ <div class="backprop-visualization">
182
+ <div class="animation-container">
183
+ <canvas id="backprop-canvas"></canvas>
184
+ <div class="animation-overlay">
185
+ <div id="current-step-info">
186
+ <h4>Step: <span id="step-name">Forward Pass</span></h4>
187
+ <p id="step-description">Data is flowing through the network...</p>
188
+ </div>
189
+ </div>
190
+ </div>
191
+
192
+ <div class="formula-section">
193
+ <h3>Mathematical Insight</h3>
194
+ <div class="formula-display">
195
+ <div class="formula">
196
+ <span class="formula-title">Gradient Descent Update:</span>
197
+ <span class="formula-math">w = w - η ∇L(w)</span>
198
+ </div>
199
+ <div class="formula">
200
+ <span class="formula-title">Chain Rule:</span>
201
+ <span class="formula-math">∂L/∂w = (∂L/∂y) × (∂y/∂w)</span>
202
+ </div>
203
+ </div>
204
+ <p class="formula-explanation">
205
+ The gradient (∇L) shows the direction of steepest increase in error.
206
+ By moving in the opposite direction, we minimize the error.
207
+ The learning rate (η) controls the step size.
208
+ </p>
209
+ </div>
210
  </div>
211
 
212
+ <div class="backprop-details-panel">
213
+ <h3>Understanding the Animation</h3>
214
+ <div class="animation-legend">
215
+ <div class="legend-item">
216
+ <div class="color-box forward-color"></div>
217
+ <span>Forward Signal Flow</span>
218
+ </div>
219
+ <div class="legend-item">
220
+ <div class="color-box error-color"></div>
221
+ <span>Error Calculation</span>
222
+ </div>
223
+ <div class="legend-item">
224
+ <div class="color-box backward-color"></div>
225
+ <span>Backward Error Propagation</span>
226
+ </div>
227
+ <div class="legend-item">
228
+ <div class="color-box update-color"></div>
229
+ <span>Weight Updates</span>
230
+ </div>
231
+ </div>
232
+
233
+ <div class="variable-display">
234
+ <h4>Current Variables</h4>
235
+ <div id="variables-container">
236
+ <!-- Will be populated by JavaScript -->
237
+ </div>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </div>
242
+
243
+ <!-- Forward Propagation Tab -->
244
+ <div class="tab-content" id="forward-propagation-tab">
245
+ <div class="container forward-container">
246
+ <div class="forward-info-panel">
247
+ <h2>Forward Propagation Explained</h2>
248
+ <p class="intro-text">
249
+ Forward propagation is the process by which input data flows through a neural network to generate predictions.
250
+ This is how neural networks make inferences after they've been trained.
251
+ </p>
252
+
253
+ <div class="forward-steps">
254
+ <h3>How Forward Propagation Works:</h3>
255
+ <ol>
256
+ <li>
257
+ <strong>Input Layer</strong>
258
+ <p>The network receives data through its input neurons.</p>
259
+ </li>
260
+ <li>
261
+ <strong>Hidden Layer Computation</strong>
262
+ <p>Each hidden neuron computes a weighted sum of inputs and applies an activation function.</p>
263
+ </li>
264
+ <li>
265
+ <strong>Output Generation</strong>
266
+ <p>The final layer produces the network's prediction or classification.</p>
267
+ </li>
268
+ </ol>
269
+ </div>
270
+
271
+ <div class="forward-controls">
272
+ <button id="start-forward-animation">Start Animation</button>
273
+ <button id="pause-forward-animation">Pause</button>
274
+ <button id="reset-forward-animation">Reset</button>
275
+ <div class="data-input-control">
276
+ <label for="input-selector">Sample Input:</label>
277
+ <select id="input-selector">
278
+ <option value="sample1">Sample 1 [0.8, 0.2, 0.5]</option>
279
+ <option value="sample2">Sample 2 [0.1, 0.9, 0.3]</option>
280
+ <option value="sample3">Sample 3 [0.5, 0.5, 0.5]</option>
281
+ </select>
282
+ </div>
283
+ </div>
284
  </div>
285
 
286
+ <div class="forward-visualization">
287
+ <div class="animation-container">
288
+ <canvas id="forward-canvas"></canvas>
289
+ <div class="animation-overlay">
290
+ <div id="forward-step-info">
291
+ <h4>Current Layer: <span id="current-layer">Input</span></h4>
292
+ <p id="forward-description">Data enters the network through the input layer.</p>
293
+ </div>
294
+ </div>
295
+ </div>
296
+
297
+ <div class="computation-section">
298
+ <h3>Computations in Detail</h3>
299
+ <div class="computation-display">
300
+ <div id="computation-formula" class="formula-math">
301
+ z = w₁x₁ + w₂x₂ + ... + wₙxₙ + b
302
+ </div>
303
+ <div id="activation-formula" class="formula-math">
304
+ a = σ(z)
305
+ </div>
306
+ </div>
307
+ <div id="computation-values">
308
+ <!-- Will be populated by JavaScript -->
309
+ </div>
310
  </div>
311
  </div>
312
+
313
+ <div class="forward-details-panel">
314
+ <h3>Network Details</h3>
315
+ <div class="network-architecture">
316
+ <h4>Architecture</h4>
317
+ <div class="architecture-info">
318
+ <p>Input layer: 3 neurons</p>
319
+ <p>Hidden layer: 4 neurons (ReLU activation)</p>
320
+ <p>Output layer: 2 neurons (Sigmoid activation)</p>
321
+ </div>
322
+ </div>
323
+
324
+ <div class="activation-functions">
325
+ <h4>Activation Functions</h4>
326
+ <div class="function-item">
327
+ <div class="function-name">ReLU:</div>
328
+ <div class="function-formula">f(x) = max(0, x)</div>
329
+ </div>
330
+ <div class="function-item">
331
+ <div class="function-name">Sigmoid:</div>
332
+ <div class="function-formula">f(x) = 1 / (1 + e<sup>-x</sup>)</div>
333
+ </div>
334
  </div>
335
  </div>
336
  </div>
337
+ </div>
338
+
339
+ <!-- Background Animation Tab -->
340
+ <div class="tab-content" id="background-animation-tab">
341
+ <div class="container background-animation-container">
342
+ <div class="background-info-panel">
343
+ <h2>Neural Network Visualization</h2>
344
+ <p class="intro-text">
345
+ This visualization represents neurons firing in a neural network. Watch as activation patterns
346
+ form and spread across the network, simulating how information flows through neural pathways.
347
+ </p>
348
+
349
+ <div class="visualization-controls">
350
+ <div class="control-group">
351
+ <h3>Visualization Controls</h3>
352
+
353
+ <div class="control-item">
354
+ <label for="neuron-count">Neuron Count:</label>
355
+ <input type="range" id="neuron-count" min="50" max="300" value="150">
356
+ <span id="neuron-count-value">150</span>
357
+ </div>
358
+
359
+ <div class="control-item">
360
+ <label for="connection-distance">Connection Distance:</label>
361
+ <input type="range" id="connection-distance" min="50" max="200" value="100">
362
+ <span id="connection-distance-value">100</span>
363
+ </div>
364
+
365
+ <div class="control-item">
366
+ <label for="firing-speed">Firing Speed:</label>
367
+ <input type="range" id="firing-speed" min="1" max="10" value="5">
368
+ <span id="firing-speed-value">5</span>
369
+ </div>
370
+
371
+ <div class="control-item">
372
+ <label for="firing-color">Firing Color:</label>
373
+ <select id="firing-color">
374
+ <option value="blue">Blue</option>
375
+ <option value="purple">Purple</option>
376
+ <option value="rainbow">Rainbow</option>
377
+ <option value="green">Green</option>
378
+ </select>
379
+ </div>
380
+ </div>
381
+
382
+ <div class="animation-buttons">
383
+ <button id="start-background-animation">Start Animation</button>
384
+ <button id="pause-background-animation">Pause</button>
385
+ <button id="reset-background-animation">Reset</button>
386
+ </div>
387
+ </div>
388
  </div>
389
 
390
+ <div class="background-visualization">
391
+ <canvas id="background-canvas"></canvas>
 
 
 
392
  </div>
393
 
394
+ <div class="background-details-panel">
395
+ <h3>About This Visualization</h3>
396
+ <p>
397
+ This animation represents a simplified view of neural activity. Each dot represents a neuron,
398
+ and the lines represent connections between neurons. When a neuron "fires," it activates
399
+ connected neurons based on the strength of their connections.
400
+ </p>
401
+ <p>
402
+ In real neural networks, neurons only fire when their activation exceeds a threshold,
403
+ and the pattern of connections is learned during training.
404
+ </p>
405
+
406
+ <div class="stats-panel">
407
+ <h4>Statistics</h4>
408
+ <div id="stats-container">
409
+ <div class="stat-item">
410
+ <div class="stat-label">Active Neurons:</div>
411
+ <div id="active-neurons-count" class="stat-value">0</div>
412
+ </div>
413
+ <div class="stat-item">
414
+ <div class="stat-label">Connections:</div>
415
+ <div id="connections-count" class="stat-value">0</div>
416
+ </div>
417
+ <div class="stat-item">
418
+ <div class="stat-label">Firing Rate:</div>
419
+ <div id="firing-rate" class="stat-value">0 Hz</div>
420
+ </div>
421
  </div>
422
  </div>
423
  </div>
 
460
  </div>
461
 
462
  <!-- Layer Editor Modal -->
463
+ <div id="layer-editor-modal" class="modal">
464
  <div class="modal-content">
465
  <span class="close-modal">&times;</span>
466
  <h2 class="modal-title">Edit Layer</h2>
467
  <form class="layer-form">
468
  <!-- Form fields will be dynamically generated based on layer type -->
469
  </form>
470
+ <div class="modal-footer">
471
+ <button type="button" class="save-layer-btn">Save Changes</button>
472
+ <button type="button" class="close-modal">Cancel</button>
473
+ </div>
474
  </div>
475
  </div>
476
  <!-- End Layer Editor Modal -->
477
 
478
+ <!-- Scripts - Note the order is important! -->
479
+ <script src="js/drag-drop-cleanup.js"></script>
480
+ <script src="js/complete-drag-fix.js"></script>
481
  <script src="js/neural-network.js"></script>
482
+ <script src="js/layer-editor.js"></script>
483
+ <script src="js/main.js"></script>
484
+ <script src="js/tab-manager.js"></script>
485
+ <script src="js/check-drag-drop.js"></script>
486
+ <script src="js/backpropagation.js"></script>
487
+ <script src="js/forward-propagation.js"></script>
488
+ <script src="js/background-animation.js"></script>
489
+ <script src="js/animation-diagnostics.js"></script>
490
+ <script src="js/animation-fixes.js"></script>
491
+ <script src="js/debug-utils.js"></script>
492
  </body>
493
  </html>
js/animation-diagnostics.js ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Animation Diagnostics Script
2
+ document.addEventListener('DOMContentLoaded', () => {
3
+ console.log('🔍 Animation Diagnostics Started');
4
+
5
+ // Check if canvas elements exist
6
+ const canvases = {
7
+ 'backprop-canvas': document.getElementById('backprop-canvas'),
8
+ 'forward-canvas': document.getElementById('forward-canvas'),
9
+ 'background-canvas': document.getElementById('background-canvas')
10
+ };
11
+
12
+ // Log results
13
+ console.log('Canvas Elements Check:');
14
+ Object.entries(canvases).forEach(([id, element]) => {
15
+ console.log(`- Canvas #${id}: ${element ? '✅ Found' : '❌ Not Found'}`);
16
+
17
+ if (element) {
18
+ // Check if canvas has dimensions
19
+ console.log(` - Dimensions: ${element.width}x${element.height}`);
20
+
21
+ // Try to get context
22
+ try {
23
+ const ctx = element.getContext('2d');
24
+ console.log(` - Context: ${ctx ? '✅ Available' : '❌ Not Available'}`);
25
+
26
+ // Test drawing something to ensure canvas works
27
+ if (ctx) {
28
+ ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
29
+ ctx.fillRect(10, 10, 50, 50);
30
+ console.log(` - Drawing test: ✅ Completed`);
31
+ }
32
+ } catch (error) {
33
+ console.error(` - Context Error: ${error.message}`);
34
+ }
35
+ }
36
+ });
37
+
38
+ // Check if animation control buttons exist
39
+ const buttons = {
40
+ 'Backpropagation': {
41
+ 'start-animation': document.getElementById('start-animation'),
42
+ 'pause-animation': document.getElementById('pause-animation'),
43
+ 'reset-animation': document.getElementById('reset-animation')
44
+ },
45
+ 'Forward Propagation': {
46
+ 'start-forward-animation': document.getElementById('start-forward-animation'),
47
+ 'pause-forward-animation': document.getElementById('pause-forward-animation'),
48
+ 'reset-forward-animation': document.getElementById('reset-forward-animation')
49
+ },
50
+ 'Background Animation': {
51
+ 'start-background-animation': document.getElementById('start-background-animation'),
52
+ 'pause-background-animation': document.getElementById('pause-background-animation'),
53
+ 'reset-background-animation': document.getElementById('reset-background-animation')
54
+ }
55
+ };
56
+
57
+ // Log results
58
+ console.log('\nAnimation Controls Check:');
59
+ Object.entries(buttons).forEach(([section, controls]) => {
60
+ console.log(`- ${section} Controls:`);
61
+ Object.entries(controls).forEach(([id, element]) => {
62
+ console.log(` - Button #${id}: ${element ? '✅ Found' : '❌ Not Found'}`);
63
+ });
64
+ });
65
+
66
+ // Check if animation scripts were loaded
67
+ const scripts = {
68
+ 'backpropagation.js': window.hasOwnProperty('backpropInitialized'),
69
+ 'forward-propagation.js': window.hasOwnProperty('forwardPropInitialized'),
70
+ 'background-animation.js': window.hasOwnProperty('backgroundAnimationInitialized')
71
+ };
72
+
73
+ console.log('\nScript Initialization Check:');
74
+ Object.entries(scripts).forEach(([script, initialized]) => {
75
+ console.log(`- ${script}: ${initialized ? '✅ Initialized' : '❌ Not Initialized'}`);
76
+ });
77
+
78
+ // Add script initialization flags to each animation script
79
+ console.log('\nAdding script initialization checks...');
80
+
81
+ // Since we can't directly modify the original scripts, we'll add these flags now
82
+ if (!window.hasOwnProperty('backpropInitialized')) {
83
+ window.backpropInitialized = false;
84
+ console.log('- Added backprop initialization check');
85
+ }
86
+
87
+ if (!window.hasOwnProperty('forwardPropInitialized')) {
88
+ window.forwardPropInitialized = false;
89
+ console.log('- Added forward-prop initialization check');
90
+ }
91
+
92
+ if (!window.hasOwnProperty('backgroundAnimationInitialized')) {
93
+ window.backgroundAnimationInitialized = false;
94
+ console.log('- Added background animation initialization check');
95
+ }
96
+
97
+ // Check tab switching
98
+ console.log('\nAdding tab switch handler...');
99
+ document.addEventListener('tabSwitch', (e) => {
100
+ console.log(`Tab switched to: ${e.detail.tab}`);
101
+
102
+ // Force redraw of canvas when tab is switched
103
+ const canvasId = `${e.detail.tab === 'backpropagation' ? 'backprop' :
104
+ e.detail.tab === 'forward-propagation' ? 'forward' :
105
+ e.detail.tab === 'background-animation' ? 'background' : ''}-canvas`;
106
+
107
+ const canvas = document.getElementById(canvasId);
108
+ if (canvas) {
109
+ console.log(`Forcing redraw of ${canvasId}`);
110
+ const ctx = canvas.getContext('2d');
111
+ const width = canvas.width;
112
+ const height = canvas.height;
113
+
114
+ // Clear canvas and draw a test pattern
115
+ ctx.clearRect(0, 0, width, height);
116
+ ctx.fillStyle = 'rgba(0, 128, 255, 0.2)';
117
+ ctx.fillRect(0, 0, width, height);
118
+
119
+ // Draw text to show it's the diagnostic render
120
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
121
+ ctx.font = '14px Arial';
122
+ ctx.textAlign = 'center';
123
+ ctx.fillText('Diagnostic Render - Animation Should Appear Here', width/2, height/2);
124
+
125
+ // Draw a boundary to show canvas size
126
+ ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
127
+ ctx.lineWidth = 2;
128
+ ctx.strokeRect(0, 0, width, height);
129
+ }
130
+ });
131
+
132
+ console.log('🔍 Animation Diagnostics Initialized');
133
+ });
js/animation-fixes.js ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Animation Fixes Script - Patches common animation issues
2
+ (function() {
3
+ console.log('Animation Fixes Script Loaded');
4
+
5
+ // Wait for DOM content to be loaded
6
+ document.addEventListener('DOMContentLoaded', function() {
7
+ console.log('Applying animation fixes');
8
+
9
+ // 1. Fix for missing canvas elements
10
+ const expectedCanvases = [
11
+ { id: 'backprop-canvas', container: '.animation-container', tab: 'backpropagation-tab' },
12
+ { id: 'forward-canvas', container: '.forward-visualization', tab: 'forward-propagation-tab' },
13
+ { id: 'background-canvas', container: '.background-visualization', tab: 'background-animation-tab' }
14
+ ];
15
+
16
+ expectedCanvases.forEach(canvasInfo => {
17
+ const canvas = document.getElementById(canvasInfo.id);
18
+ const container = document.querySelector(`#${canvasInfo.tab} ${canvasInfo.container}`);
19
+
20
+ if (!canvas && container) {
21
+ console.log(`Creating missing canvas: ${canvasInfo.id}`);
22
+ const newCanvas = document.createElement('canvas');
23
+ newCanvas.id = canvasInfo.id;
24
+ newCanvas.className = 'animation-canvas';
25
+ newCanvas.width = container.clientWidth || 800;
26
+ newCanvas.height = container.clientHeight || 400;
27
+
28
+ container.prepend(newCanvas);
29
+
30
+ // Try drawing something to show the canvas is working
31
+ const ctx = newCanvas.getContext('2d');
32
+ if (ctx) {
33
+ ctx.fillStyle = 'rgba(200, 200, 255, 0.3)';
34
+ ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);
35
+ ctx.fillStyle = '#333';
36
+ ctx.font = '16px Arial';
37
+ ctx.textAlign = 'center';
38
+ ctx.fillText(`Canvas ${canvasInfo.id} initialized`, newCanvas.width/2, newCanvas.height/2);
39
+ }
40
+ }
41
+ });
42
+
43
+ // 2. Fix for animation scripts not running
44
+ // Capture tab change events and force canvas initialization
45
+ document.addEventListener('tabSwitch', function(e) {
46
+ const tabId = e.detail.tab;
47
+ console.log(`Tab switch detected to: ${tabId}`);
48
+
49
+ setTimeout(() => {
50
+ // Force canvas initialization after tab switch
51
+ switch(tabId) {
52
+ case 'backpropagation':
53
+ initBackpropCanvas();
54
+ break;
55
+ case 'forward-propagation':
56
+ initForwardCanvas();
57
+ break;
58
+ case 'background-animation':
59
+ initBackgroundCanvas();
60
+ break;
61
+ }
62
+ }, 100);
63
+ });
64
+
65
+ // Helper functions to initialize canvases if the main scripts fail
66
+ function initBackpropCanvas() {
67
+ const canvas = document.getElementById('backprop-canvas');
68
+ if (!canvas) return;
69
+
70
+ if (typeof window.initBackpropCanvas === 'function') {
71
+ window.initBackpropCanvas();
72
+ } else {
73
+ const ctx = canvas.getContext('2d');
74
+ if (ctx) {
75
+ drawPlaceholderNetwork(ctx, canvas.width, canvas.height, 'Backpropagation');
76
+ }
77
+ }
78
+ }
79
+
80
+ function initForwardCanvas() {
81
+ const canvas = document.getElementById('forward-canvas');
82
+ if (!canvas) return;
83
+
84
+ if (typeof window.initForwardPropCanvas === 'function') {
85
+ window.initForwardPropCanvas();
86
+ } else {
87
+ const ctx = canvas.getContext('2d');
88
+ if (ctx) {
89
+ drawPlaceholderNetwork(ctx, canvas.width, canvas.height, 'Forward Propagation');
90
+ }
91
+ }
92
+ }
93
+
94
+ function initBackgroundCanvas() {
95
+ const canvas = document.getElementById('background-canvas');
96
+ if (!canvas) return;
97
+
98
+ if (typeof window.initBackgroundCanvas === 'function') {
99
+ window.initBackgroundCanvas();
100
+ } else {
101
+ const ctx = canvas.getContext('2d');
102
+ if (ctx) {
103
+ drawPlaceholderNeurons(ctx, canvas.width, canvas.height);
104
+ }
105
+ }
106
+ }
107
+
108
+ // Helper drawing functions
109
+ function drawPlaceholderNetwork(ctx, width, height, title) {
110
+ // Clear canvas
111
+ ctx.clearRect(0, 0, width, height);
112
+
113
+ // Draw background
114
+ ctx.fillStyle = '#f8f9fa';
115
+ ctx.fillRect(0, 0, width, height);
116
+
117
+ // Define network layout
118
+ const layers = [3, 4, 2]; // Input, hidden, output layers
119
+ const neuronRadius = 20;
120
+ const layerSpacing = width / (layers.length + 1);
121
+
122
+ // Function to calculate neuron positions
123
+ function getNeuronPosition(layerIndex, neuronIndex, totalNeurons) {
124
+ const x = layerSpacing * (layerIndex + 1);
125
+ const layerHeight = totalNeurons * (neuronRadius * 2 + 10);
126
+ const startY = (height - layerHeight) / 2 + neuronRadius;
127
+ const y = startY + neuronIndex * (neuronRadius * 2 + 10);
128
+ return { x, y };
129
+ }
130
+
131
+ // Draw connections first (so they appear behind neurons)
132
+ ctx.strokeStyle = '#aaa';
133
+ ctx.lineWidth = 1;
134
+
135
+ // For each layer except the last
136
+ for (let layerIndex = 0; layerIndex < layers.length - 1; layerIndex++) {
137
+ const sourceLayer = layers[layerIndex];
138
+ const targetLayer = layers[layerIndex + 1];
139
+
140
+ // Connect each neuron in source layer to each neuron in target layer
141
+ for (let sourceNeuron = 0; sourceNeuron < sourceLayer; sourceNeuron++) {
142
+ const source = getNeuronPosition(layerIndex, sourceNeuron, sourceLayer);
143
+
144
+ for (let targetNeuron = 0; targetNeuron < targetLayer; targetNeuron++) {
145
+ const target = getNeuronPosition(layerIndex + 1, targetNeuron, targetLayer);
146
+
147
+ // Draw connection
148
+ ctx.beginPath();
149
+ ctx.moveTo(source.x, source.y);
150
+ ctx.lineTo(target.x, target.y);
151
+ ctx.stroke();
152
+ }
153
+ }
154
+ }
155
+
156
+ // Draw neurons
157
+ const layerColors = ['#6495ED', '#7B68EE', '#9370DB']; // Different color for each layer
158
+
159
+ for (let layerIndex = 0; layerIndex < layers.length; layerIndex++) {
160
+ const neuronsInLayer = layers[layerIndex];
161
+
162
+ for (let neuronIndex = 0; neuronIndex < neuronsInLayer; neuronIndex++) {
163
+ const { x, y } = getNeuronPosition(layerIndex, neuronIndex, neuronsInLayer);
164
+
165
+ // Draw neuron circle
166
+ ctx.beginPath();
167
+ ctx.arc(x, y, neuronRadius, 0, Math.PI * 2);
168
+ ctx.fillStyle = layerColors[layerIndex];
169
+ ctx.fill();
170
+ ctx.strokeStyle = '#fff';
171
+ ctx.lineWidth = 2;
172
+ ctx.stroke();
173
+ }
174
+ }
175
+
176
+ // Add title
177
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
178
+ ctx.font = 'bold 20px Arial';
179
+ ctx.textAlign = 'center';
180
+ ctx.fillText(title + ' Animation', width/2, 40);
181
+
182
+ // Add message
183
+ ctx.font = '16px Arial';
184
+ ctx.fillText('Animation placeholder - Check console for errors', width/2, height - 30);
185
+ }
186
+
187
+ function drawPlaceholderNeurons(ctx, width, height) {
188
+ // Clear canvas
189
+ ctx.clearRect(0, 0, width, height);
190
+
191
+ // Draw background
192
+ ctx.fillStyle = '#f8f9fa';
193
+ ctx.fillRect(0, 0, width, height);
194
+
195
+ // Create random neurons
196
+ const neurons = [];
197
+ const neuronCount = 50;
198
+
199
+ for (let i = 0; i < neuronCount; i++) {
200
+ neurons.push({
201
+ x: Math.random() * width,
202
+ y: Math.random() * height,
203
+ radius: 3 + Math.random() * 5,
204
+ color: Math.random() > 0.8 ? '#6495ED' : '#aaaaaa'
205
+ });
206
+ }
207
+
208
+ // Draw connections
209
+ ctx.strokeStyle = 'rgba(170, 170, 170, 0.3)';
210
+ ctx.lineWidth = 1;
211
+
212
+ for (let i = 0; i < neurons.length; i++) {
213
+ const source = neurons[i];
214
+
215
+ // Connect to nearby neurons
216
+ for (let j = i + 1; j < neurons.length; j++) {
217
+ const target = neurons[j];
218
+ const distance = Math.sqrt(
219
+ Math.pow(target.x - source.x, 2) +
220
+ Math.pow(target.y - source.y, 2)
221
+ );
222
+
223
+ // Only connect neurons that are close enough
224
+ if (distance < 100) {
225
+ ctx.beginPath();
226
+ ctx.moveTo(source.x, source.y);
227
+ ctx.lineTo(target.x, target.y);
228
+ ctx.stroke();
229
+ }
230
+ }
231
+ }
232
+
233
+ // Draw neurons
234
+ neurons.forEach(neuron => {
235
+ ctx.beginPath();
236
+ ctx.arc(neuron.x, neuron.y, neuron.radius, 0, Math.PI * 2);
237
+ ctx.fillStyle = neuron.color;
238
+ ctx.fill();
239
+ });
240
+
241
+ // Add title
242
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
243
+ ctx.font = 'bold 20px Arial';
244
+ ctx.textAlign = 'center';
245
+ ctx.fillText('Neural Background Animation', width/2, 40);
246
+
247
+ // Add message
248
+ ctx.font = '16px Arial';
249
+ ctx.fillText('Animation placeholder - Check console for errors', width/2, height - 30);
250
+ }
251
+
252
+ // Initial setup - activate the currently selected tab
253
+ const activeTabButton = document.querySelector('.tab-button.active');
254
+ if (activeTabButton) {
255
+ const tabId = activeTabButton.getAttribute('data-tab');
256
+ console.log(`Initial active tab: ${tabId}`);
257
+
258
+ // If the tab manager is loaded, use its function
259
+ if (window.activateTab) {
260
+ window.activateTab(tabId);
261
+ } else {
262
+ // Fallback - directly trigger the tab switch event
263
+ document.dispatchEvent(new CustomEvent('tabSwitch', {
264
+ detail: { tab: tabId }
265
+ }));
266
+ }
267
+ }
268
+ });
269
+ })();
js/background-animation.js ADDED
@@ -0,0 +1,552 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Neural Network Background Animation
2
+ document.addEventListener('DOMContentLoaded', () => {
3
+ // Set initialization flag
4
+ window.backgroundAnimationInitialized = true;
5
+ console.log('Background animation script initialized');
6
+
7
+ // Canvas initialization function
8
+ function initializeCanvas() {
9
+ console.log('Initializing background animation canvas');
10
+ const canvas = document.getElementById('background-canvas');
11
+ if (!canvas) {
12
+ console.error('Background animation canvas not found!');
13
+ return;
14
+ }
15
+
16
+ const ctx = canvas.getContext('2d');
17
+ if (!ctx) {
18
+ console.error('Could not get 2D context for background animation canvas');
19
+ return;
20
+ }
21
+
22
+ // Set canvas dimensions
23
+ const container = canvas.parentElement;
24
+ if (container) {
25
+ canvas.width = container.clientWidth || 800;
26
+ canvas.height = container.clientHeight || 400;
27
+ } else {
28
+ canvas.width = 800;
29
+ canvas.height = 400;
30
+ }
31
+
32
+ // Clear canvas
33
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
34
+
35
+ // Reset animation state and redraw
36
+ resetAnimation();
37
+ updateCanvas(); // Initial draw
38
+ }
39
+
40
+ // Register the canvas initialization function with tab manager
41
+ if (typeof window !== 'undefined') {
42
+ window.initBackgroundCanvas = initializeCanvas;
43
+ }
44
+
45
+ // Canvas and context
46
+ const canvas = document.getElementById('background-canvas');
47
+ const ctx = canvas.getContext('2d');
48
+
49
+ // Control elements
50
+ const startButton = document.getElementById('start-background-animation');
51
+ const pauseButton = document.getElementById('pause-background-animation');
52
+ const resetButton = document.getElementById('reset-background-animation');
53
+
54
+ // Slider controls
55
+ const neuronCountSlider = document.getElementById('neuron-count');
56
+ const neuronCountValue = document.getElementById('neuron-count-value');
57
+ const connectionDistanceSlider = document.getElementById('connection-distance');
58
+ const connectionDistanceValue = document.getElementById('connection-distance-value');
59
+ const firingSpeedSlider = document.getElementById('firing-speed');
60
+ const firingSpeedValue = document.getElementById('firing-speed-value');
61
+ const firingColorSelect = document.getElementById('firing-color');
62
+
63
+ // Stats display elements
64
+ const activeNeuronsCount = document.getElementById('active-neurons-count');
65
+ const connectionsCount = document.getElementById('connections-count');
66
+ const firingRateElement = document.getElementById('firing-rate');
67
+
68
+ // Animation state
69
+ let animationState = {
70
+ running: false,
71
+ neurons: [],
72
+ connections: [],
73
+ config: {
74
+ neuronCount: 150,
75
+ connectionDistance: 100,
76
+ firingSpeed: 5,
77
+ firingColor: 'blue'
78
+ },
79
+ stats: {
80
+ activeNeurons: 0,
81
+ connectionCount: 0,
82
+ firingRate: 0,
83
+ firingHistory: []
84
+ },
85
+ animationFrameId: null,
86
+ lastTimestamp: 0
87
+ };
88
+
89
+ // Neuron class
90
+ class Neuron {
91
+ constructor(x, y) {
92
+ this.x = x;
93
+ this.y = y;
94
+ this.radius = 3 + Math.random() * 2; // 3-5 pixels
95
+ this.connections = [];
96
+ this.firing = false;
97
+ this.fireProgress = 0; // 0-1 for animation progress
98
+ this.lastFireTime = 0;
99
+ this.refractionPeriod = 500 + Math.random() * 1500; // 0.5-2 seconds
100
+ this.activated = false;
101
+ this.activationLevel = 0; // 0-1
102
+ this.threshold = 0.4 + Math.random() * 0.3; // 0.4-0.7
103
+ this.speedMultiplier = 0.8 + Math.random() * 0.4; // 0.8-1.2x speed
104
+ this.activationDecay = 0.02; // How fast activation decreases
105
+ }
106
+
107
+ // Add a connection to another neuron
108
+ addConnection(neuron, strength) {
109
+ this.connections.push({
110
+ target: neuron,
111
+ strength: strength,
112
+ active: false,
113
+ progress: 0
114
+ });
115
+ }
116
+
117
+ // Update neuron state
118
+ update(deltaTime, speed) {
119
+ // Scale the time increment based on speed (0-10)
120
+ const timeIncrement = deltaTime * (speed / 5);
121
+
122
+ // Update activation level (decay over time)
123
+ if (this.activationLevel > 0 && !this.firing) {
124
+ this.activationLevel = Math.max(0, this.activationLevel - this.activationDecay * (timeIncrement / 16));
125
+ }
126
+
127
+ // Check if neuron should fire
128
+ const currentTime = Date.now();
129
+ if (!this.firing && this.activationLevel >= this.threshold &&
130
+ (currentTime - this.lastFireTime > this.refractionPeriod)) {
131
+ this.firing = true;
132
+ this.fireProgress = 0;
133
+ this.lastFireTime = currentTime;
134
+ animationState.stats.firingHistory.push(currentTime);
135
+ }
136
+
137
+ // Update firing animation
138
+ if (this.firing) {
139
+ this.fireProgress += 0.05 * this.speedMultiplier * (timeIncrement / 16);
140
+
141
+ // Activate connections when fireProgress reaches 1
142
+ if (this.fireProgress >= 1) {
143
+ this.firing = false;
144
+ this.activationLevel = 0; // Reset activation after firing
145
+
146
+ // Activate outgoing connections
147
+ this.connections.forEach(conn => {
148
+ if (Math.random() < conn.strength) {
149
+ conn.active = true;
150
+ conn.progress = 0;
151
+ }
152
+ });
153
+ }
154
+ }
155
+
156
+ // Update connection animations
157
+ this.connections.forEach(conn => {
158
+ if (conn.active) {
159
+ conn.progress += 0.04 * this.speedMultiplier * (timeIncrement / 16);
160
+
161
+ // Activate target neuron when signal reaches the end
162
+ if (conn.progress >= 1) {
163
+ conn.active = false;
164
+ conn.target.activationLevel += conn.strength;
165
+ }
166
+ }
167
+ });
168
+ }
169
+
170
+ // Draw the neuron
171
+ draw(ctx, colorScheme) {
172
+ // Draw connections
173
+ this.connections.forEach(conn => {
174
+ const { target, active, progress, strength } = conn;
175
+
176
+ // Set line style based on connection state
177
+ if (active) {
178
+ // Active connection (signal traveling)
179
+ const lineWidth = 1 + strength * 1.5;
180
+
181
+ // Calculate position along the line based on progress
182
+ const signalX = this.x + (target.x - this.x) * progress;
183
+ const signalY = this.y + (target.y - this.y) * progress;
184
+
185
+ // Draw the inactive part of the connection
186
+ ctx.beginPath();
187
+ ctx.moveTo(this.x, this.y);
188
+ ctx.lineTo(target.x, target.y);
189
+ ctx.strokeStyle = `rgba(200, 200, 200, ${strength * 0.3})`;
190
+ ctx.lineWidth = 0.5;
191
+ ctx.stroke();
192
+
193
+ // Draw the active signal
194
+ ctx.beginPath();
195
+ ctx.arc(signalX, signalY, lineWidth + 1, 0, Math.PI * 2);
196
+
197
+ // Use the appropriate color based on the selected scheme
198
+ if (colorScheme === 'rainbow') {
199
+ const hue = (Date.now() / 20) % 360;
200
+ ctx.fillStyle = `hsl(${hue}, 80%, 60%)`;
201
+ } else {
202
+ switch (colorScheme) {
203
+ case 'blue':
204
+ ctx.fillStyle = `rgba(52, 152, 219, ${0.7 + progress * 0.3})`;
205
+ break;
206
+ case 'purple':
207
+ ctx.fillStyle = `rgba(155, 89, 182, ${0.7 + progress * 0.3})`;
208
+ break;
209
+ case 'green':
210
+ ctx.fillStyle = `rgba(46, 204, 113, ${0.7 + progress * 0.3})`;
211
+ break;
212
+ default:
213
+ ctx.fillStyle = `rgba(52, 152, 219, ${0.7 + progress * 0.3})`;
214
+ }
215
+ }
216
+ ctx.fill();
217
+
218
+ } else if (this.firing || this.activationLevel > 0.2) {
219
+ // Connection from an active neuron
220
+ ctx.beginPath();
221
+ ctx.moveTo(this.x, this.y);
222
+ ctx.lineTo(target.x, target.y);
223
+ ctx.strokeStyle = `rgba(200, 200, 200, ${strength * 0.5})`;
224
+ ctx.lineWidth = 0.5;
225
+ ctx.stroke();
226
+ } else {
227
+ // Inactive connection
228
+ ctx.beginPath();
229
+ ctx.moveTo(this.x, this.y);
230
+ ctx.lineTo(target.x, target.y);
231
+ ctx.strokeStyle = `rgba(200, 200, 200, ${strength * 0.2})`;
232
+ ctx.lineWidth = 0.2;
233
+ ctx.stroke();
234
+ }
235
+ });
236
+
237
+ // Draw the neuron body
238
+ ctx.beginPath();
239
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
240
+
241
+ // Color based on firing/activation state
242
+ if (this.firing) {
243
+ // Use the selected color scheme for firing neurons
244
+ if (colorScheme === 'rainbow') {
245
+ const hue = (Date.now() / 20) % 360;
246
+ ctx.fillStyle = `hsl(${hue}, 80%, 60%)`;
247
+ } else {
248
+ switch (colorScheme) {
249
+ case 'blue':
250
+ ctx.fillStyle = `rgba(52, 152, 219, ${0.7 + this.fireProgress * 0.3})`;
251
+ break;
252
+ case 'purple':
253
+ ctx.fillStyle = `rgba(155, 89, 182, ${0.7 + this.fireProgress * 0.3})`;
254
+ break;
255
+ case 'green':
256
+ ctx.fillStyle = `rgba(46, 204, 113, ${0.7 + this.fireProgress * 0.3})`;
257
+ break;
258
+ default:
259
+ ctx.fillStyle = `rgba(52, 152, 219, ${0.7 + this.fireProgress * 0.3})`;
260
+ }
261
+ }
262
+
263
+ // Add glow effect for firing neurons
264
+ ctx.shadowColor = ctx.fillStyle;
265
+ ctx.shadowBlur = 10;
266
+ } else if (this.activationLevel > 0) {
267
+ // Partially activated
268
+ ctx.fillStyle = `rgba(127, 140, 141, ${0.3 + this.activationLevel * 0.7})`;
269
+ ctx.shadowBlur = 0;
270
+ } else {
271
+ // Inactive
272
+ ctx.fillStyle = 'rgba(127, 140, 141, 0.2)';
273
+ ctx.shadowBlur = 0;
274
+ }
275
+
276
+ ctx.fill();
277
+ ctx.shadowBlur = 0; // Reset shadow blur
278
+ }
279
+ }
280
+
281
+ // Initialize the network
282
+ function initNetwork() {
283
+ // Clear existing state
284
+ animationState.neurons = [];
285
+ animationState.connections = [];
286
+ animationState.stats.activeNeurons = 0;
287
+ animationState.stats.connectionCount = 0;
288
+ animationState.stats.firingHistory = [];
289
+
290
+ const { neuronCount, connectionDistance } = animationState.config;
291
+
292
+ // Create neurons with random positions
293
+ for (let i = 0; i < neuronCount; i++) {
294
+ const x = Math.random() * canvas.width;
295
+ const y = Math.random() * canvas.height;
296
+ animationState.neurons.push(new Neuron(x, y));
297
+ }
298
+
299
+ // Create connections between nearby neurons
300
+ let connectionCount = 0;
301
+
302
+ animationState.neurons.forEach(neuron => {
303
+ animationState.neurons.forEach(target => {
304
+ if (neuron !== target) {
305
+ // Calculate distance between neurons
306
+ const dx = neuron.x - target.x;
307
+ const dy = neuron.y - target.y;
308
+ const distance = Math.sqrt(dx * dx + dy * dy);
309
+
310
+ // Connect if within range (with random chance based on distance)
311
+ if (distance < connectionDistance) {
312
+ const probability = 1 - (distance / connectionDistance);
313
+ if (Math.random() < probability * 0.3) {
314
+ // Connection strength decreases with distance
315
+ const strength = 0.2 + (1 - distance / connectionDistance) * 0.6;
316
+ neuron.addConnection(target, strength);
317
+ connectionCount++;
318
+ }
319
+ }
320
+ }
321
+ });
322
+ });
323
+
324
+ animationState.stats.connectionCount = connectionCount;
325
+
326
+ // Randomly activate a few neurons to start
327
+ for (let i = 0; i < 3; i++) {
328
+ const randomIndex = Math.floor(Math.random() * animationState.neurons.length);
329
+ animationState.neurons[randomIndex].firing = true;
330
+ animationState.neurons[randomIndex].lastFireTime = Date.now();
331
+ }
332
+
333
+ // Update stats display
334
+ updateStatsDisplay();
335
+ }
336
+
337
+ // Canvas resize functionality
338
+ function resizeCanvas() {
339
+ const container = canvas.parentElement;
340
+ canvas.width = container.clientWidth;
341
+ canvas.height = container.clientHeight;
342
+
343
+ // Reinitialize the network when the canvas is resized
344
+ if (animationState.neurons.length > 0) {
345
+ initNetwork();
346
+ }
347
+ }
348
+
349
+ // Update stats display
350
+ function updateStatsDisplay() {
351
+ if (!activeNeuronsCount || !connectionsCount || !firingRateElement) return;
352
+
353
+ // Count active neurons
354
+ const activeCount = animationState.neurons.filter(n => n.firing || n.activationLevel > 0.2).length;
355
+ animationState.stats.activeNeurons = activeCount;
356
+
357
+ // Calculate firing rate (fires per second)
358
+ const now = Date.now();
359
+ const recentFirings = animationState.stats.firingHistory.filter(time => now - time < 1000).length;
360
+ animationState.stats.firingRate = recentFirings;
361
+
362
+ // Clean up old firing history
363
+ animationState.stats.firingHistory = animationState.stats.firingHistory.filter(time => now - time < 1000);
364
+
365
+ // Update display
366
+ activeNeuronsCount.textContent = activeCount;
367
+ connectionsCount.textContent = animationState.stats.connectionCount;
368
+ firingRateElement.textContent = `${recentFirings} Hz`;
369
+ }
370
+
371
+ // Animation loop
372
+ function animate(timestamp) {
373
+ if (!animationState.running) return;
374
+
375
+ // Calculate delta time
376
+ const deltaTime = timestamp - (animationState.lastTimestamp || timestamp);
377
+ animationState.lastTimestamp = timestamp;
378
+
379
+ // Clear canvas
380
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
381
+
382
+ // Update and draw neurons
383
+ animationState.neurons.forEach(neuron => {
384
+ neuron.update(deltaTime, animationState.config.firingSpeed);
385
+ neuron.draw(ctx, animationState.config.firingColor);
386
+ });
387
+
388
+ // Randomly activate neurons occasionally
389
+ if (Math.random() < 0.01 * (animationState.config.firingSpeed / 5)) {
390
+ const randomIndex = Math.floor(Math.random() * animationState.neurons.length);
391
+ const randomNeuron = animationState.neurons[randomIndex];
392
+
393
+ if (!randomNeuron.firing && Date.now() - randomNeuron.lastFireTime > randomNeuron.refractionPeriod) {
394
+ randomNeuron.activationLevel = randomNeuron.threshold;
395
+ }
396
+ }
397
+
398
+ // Update stats periodically (every ~500ms)
399
+ if (timestamp % 500 < 20) {
400
+ updateStatsDisplay();
401
+ }
402
+
403
+ // Continue animation
404
+ animationState.animationFrameId = requestAnimationFrame(animate);
405
+ }
406
+
407
+ // Start animation
408
+ function startAnimation() {
409
+ if (!animationState.running) {
410
+ animationState.running = true;
411
+ animationState.lastTimestamp = 0;
412
+ animationState.animationFrameId = requestAnimationFrame(animate);
413
+
414
+ startButton.disabled = true;
415
+ pauseButton.disabled = false;
416
+ resetButton.disabled = false;
417
+ }
418
+ }
419
+
420
+ // Pause animation
421
+ function pauseAnimation() {
422
+ if (animationState.running) {
423
+ animationState.running = false;
424
+ if (animationState.animationFrameId) {
425
+ cancelAnimationFrame(animationState.animationFrameId);
426
+ }
427
+
428
+ startButton.disabled = false;
429
+ pauseButton.disabled = true;
430
+ resetButton.disabled = false;
431
+ }
432
+ }
433
+
434
+ // Reset animation
435
+ function resetAnimation() {
436
+ pauseAnimation();
437
+ initNetwork();
438
+
439
+ startButton.disabled = false;
440
+ pauseButton.disabled = true;
441
+ resetButton.disabled = false;
442
+ }
443
+
444
+ // Initialize the visualization
445
+ function initVisualization() {
446
+ if (!canvas) return;
447
+
448
+ resizeCanvas();
449
+ window.addEventListener('resize', resizeCanvas);
450
+
451
+ // Set up initial configuration from sliders if they exist
452
+ if (neuronCountSlider) {
453
+ animationState.config.neuronCount = parseInt(neuronCountSlider.value, 10);
454
+ neuronCountValue.textContent = animationState.config.neuronCount;
455
+ }
456
+
457
+ if (connectionDistanceSlider) {
458
+ animationState.config.connectionDistance = parseInt(connectionDistanceSlider.value, 10);
459
+ connectionDistanceValue.textContent = animationState.config.connectionDistance;
460
+ }
461
+
462
+ if (firingSpeedSlider) {
463
+ animationState.config.firingSpeed = parseInt(firingSpeedSlider.value, 10);
464
+ firingSpeedValue.textContent = animationState.config.firingSpeed;
465
+ }
466
+
467
+ if (firingColorSelect) {
468
+ animationState.config.firingColor = firingColorSelect.value;
469
+ }
470
+
471
+ // Initialize the network
472
+ initNetwork();
473
+
474
+ // Set button states
475
+ startButton.disabled = false;
476
+ pauseButton.disabled = true;
477
+ resetButton.disabled = true;
478
+ }
479
+
480
+ // Handle configuration changes
481
+ function setupControlListeners() {
482
+ if (neuronCountSlider) {
483
+ neuronCountSlider.addEventListener('input', () => {
484
+ animationState.config.neuronCount = parseInt(neuronCountSlider.value, 10);
485
+ neuronCountValue.textContent = animationState.config.neuronCount;
486
+ });
487
+
488
+ neuronCountSlider.addEventListener('change', () => {
489
+ // Only reinitialize network when slider interaction ends
490
+ resetAnimation();
491
+ });
492
+ }
493
+
494
+ if (connectionDistanceSlider) {
495
+ connectionDistanceSlider.addEventListener('input', () => {
496
+ animationState.config.connectionDistance = parseInt(connectionDistanceSlider.value, 10);
497
+ connectionDistanceValue.textContent = animationState.config.connectionDistance;
498
+ });
499
+
500
+ connectionDistanceSlider.addEventListener('change', () => {
501
+ resetAnimation();
502
+ });
503
+ }
504
+
505
+ if (firingSpeedSlider) {
506
+ firingSpeedSlider.addEventListener('input', () => {
507
+ animationState.config.firingSpeed = parseInt(firingSpeedSlider.value, 10);
508
+ firingSpeedValue.textContent = animationState.config.firingSpeed;
509
+ });
510
+ }
511
+
512
+ if (firingColorSelect) {
513
+ firingColorSelect.addEventListener('change', () => {
514
+ animationState.config.firingColor = firingColorSelect.value;
515
+ });
516
+ }
517
+
518
+ // Button event listeners
519
+ if (startButton) {
520
+ startButton.addEventListener('click', startAnimation);
521
+ }
522
+
523
+ if (pauseButton) {
524
+ pauseButton.addEventListener('click', pauseAnimation);
525
+ }
526
+
527
+ if (resetButton) {
528
+ resetButton.addEventListener('click', resetAnimation);
529
+ }
530
+
531
+ // Tab switching event from the main tab controller
532
+ document.addEventListener('tabSwitch', (e) => {
533
+ if (e.detail.tab === 'background-animation') {
534
+ // Restart animation when switching to this tab
535
+ if (animationState.neurons.length === 0) {
536
+ initNetwork();
537
+ }
538
+
539
+ if (!animationState.running) {
540
+ startAnimation();
541
+ }
542
+ } else if (animationState.running) {
543
+ // Pause animation when switching away from this tab
544
+ pauseAnimation();
545
+ }
546
+ });
547
+ }
548
+
549
+ // Initialize everything
550
+ initVisualization();
551
+ setupControlListeners();
552
+ });
js/backpropagation.js ADDED
@@ -0,0 +1,563 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Backpropagation Animation and Tab Functionality
2
+ document.addEventListener('DOMContentLoaded', () => {
3
+ // Set initialization flag
4
+ window.backpropInitialized = true;
5
+ console.log('Backpropagation script initialized');
6
+
7
+ // Canvas initialization function
8
+ function initializeCanvas() {
9
+ console.log('Initializing backpropagation canvas');
10
+ const canvas = document.getElementById('backprop-canvas');
11
+ if (!canvas) {
12
+ console.error('Backpropagation canvas not found!');
13
+ return;
14
+ }
15
+
16
+ const ctx = canvas.getContext('2d');
17
+ if (!ctx) {
18
+ console.error('Could not get 2D context for backpropagation canvas');
19
+ return;
20
+ }
21
+
22
+ // Set canvas dimensions
23
+ const container = canvas.parentElement;
24
+ if (container) {
25
+ canvas.width = container.clientWidth || 800;
26
+ canvas.height = container.clientHeight || 400;
27
+ } else {
28
+ canvas.width = 800;
29
+ canvas.height = 400;
30
+ }
31
+
32
+ // Clear canvas
33
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
34
+
35
+ // Reset animation state and redraw
36
+ resetAnimation();
37
+ drawNetwork();
38
+ }
39
+
40
+ // Register the canvas initialization function with tab manager
41
+ if (typeof window !== 'undefined') {
42
+ window.initBackpropCanvas = initializeCanvas;
43
+ }
44
+
45
+ // Tab functionality
46
+ const tabButtons = document.querySelectorAll('.tab-button');
47
+ const tabContents = document.querySelectorAll('.tab-content');
48
+
49
+ tabButtons.forEach(button => {
50
+ button.addEventListener('click', () => {
51
+ // Remove active class from all tabs
52
+ tabButtons.forEach(btn => btn.classList.remove('active'));
53
+ tabContents.forEach(content => content.classList.remove('active'));
54
+
55
+ // Add active class to clicked tab
56
+ button.classList.add('active');
57
+ const tabId = button.getAttribute('data-tab');
58
+ document.getElementById(`${tabId}-tab`).classList.add('active');
59
+
60
+ // If switching to backpropagation tab, reset the animation
61
+ if (tabId === 'backpropagation') {
62
+ resetAnimation();
63
+ }
64
+ });
65
+ });
66
+
67
+ // Backpropagation Animation Setup
68
+ const canvas = document.getElementById('backprop-canvas');
69
+ const ctx = canvas.getContext('2d');
70
+
71
+ // Animation control buttons
72
+ const startButton = document.getElementById('start-animation');
73
+ const pauseButton = document.getElementById('pause-animation');
74
+ const resetButton = document.getElementById('reset-animation');
75
+ const speedControl = document.getElementById('animation-speed');
76
+
77
+ // Animation state
78
+ let animationState = {
79
+ running: false,
80
+ currentStep: 0,
81
+ speed: 5,
82
+ animationFrameId: null,
83
+ network: null,
84
+ lastTimestamp: 0
85
+ };
86
+
87
+ // Sample neural network for demonstration
88
+ class NeuralNetwork {
89
+ constructor() {
90
+ // Simple network with input, hidden and output layers
91
+ this.layers = [
92
+ { type: 'input', neurons: 3, activation: 'none' },
93
+ { type: 'hidden', neurons: 4, activation: 'relu' },
94
+ { type: 'output', neurons: 2, activation: 'sigmoid' }
95
+ ];
96
+
97
+ // Initialize weights with random values
98
+ this.weights = [
99
+ this.generateRandomWeights(3, 4), // Input to Hidden
100
+ this.generateRandomWeights(4, 2) // Hidden to Output
101
+ ];
102
+
103
+ // Initialize biases
104
+ this.biases = [
105
+ Array(4).fill(0).map(() => Math.random() * 0.2 - 0.1), // Hidden layer biases
106
+ Array(2).fill(0).map(() => Math.random() * 0.2 - 0.1) // Output layer biases
107
+ ];
108
+
109
+ // For animation purposes
110
+ this.activations = [
111
+ Array(3).fill(0), // Input activations
112
+ Array(4).fill(0), // Hidden layer activations
113
+ Array(2).fill(0) // Output activations
114
+ ];
115
+
116
+ this.gradients = [
117
+ Array(3 * 4).fill(0), // Input to Hidden gradients
118
+ Array(4 * 2).fill(0) // Hidden to Output gradients
119
+ ];
120
+
121
+ // Expected output for the sample
122
+ this.expectedOutput = [1, 0];
123
+
124
+ // Sample input
125
+ this.sampleInput = [0.8, 0.2, 0.5];
126
+
127
+ // Error
128
+ this.error = 0;
129
+ }
130
+
131
+ generateRandomWeights(inputSize, outputSize) {
132
+ const weights = [];
133
+ for (let i = 0; i < inputSize * outputSize; i++) {
134
+ weights.push(Math.random() * 0.4 - 0.2); // Random weights between -0.2 and 0.2
135
+ }
136
+ return weights;
137
+ }
138
+
139
+ // Activation functions
140
+ relu(x) {
141
+ return Math.max(0, x);
142
+ }
143
+
144
+ sigmoid(x) {
145
+ return 1 / (1 + Math.exp(-x));
146
+ }
147
+
148
+ // Forward pass
149
+ forwardPass() {
150
+ // Set input layer activations to sample input
151
+ this.activations[0] = [...this.sampleInput];
152
+
153
+ // Calculate hidden layer activations
154
+ for (let i = 0; i < this.layers[1].neurons; i++) {
155
+ let sum = this.biases[0][i];
156
+ for (let j = 0; j < this.layers[0].neurons; j++) {
157
+ const weightIdx = j * this.layers[1].neurons + i;
158
+ sum += this.activations[0][j] * this.weights[0][weightIdx];
159
+ }
160
+ this.activations[1][i] = this.relu(sum);
161
+ }
162
+
163
+ // Calculate output layer activations
164
+ for (let i = 0; i < this.layers[2].neurons; i++) {
165
+ let sum = this.biases[1][i];
166
+ for (let j = 0; j < this.layers[1].neurons; j++) {
167
+ const weightIdx = j * this.layers[2].neurons + i;
168
+ sum += this.activations[1][j] * this.weights[1][weightIdx];
169
+ }
170
+ this.activations[2][i] = this.sigmoid(sum);
171
+ }
172
+
173
+ // Calculate error (mean squared error)
174
+ this.error = 0;
175
+ for (let i = 0; i < this.layers[2].neurons; i++) {
176
+ const diff = this.activations[2][i] - this.expectedOutput[i];
177
+ this.error += diff * diff;
178
+ }
179
+ this.error /= this.layers[2].neurons;
180
+
181
+ return this.activations[2]; // Return output
182
+ }
183
+
184
+ // Calculate gradients (backward pass)
185
+ calculateGradients() {
186
+ // Output layer gradients
187
+ const outputDeltas = [];
188
+ for (let i = 0; i < this.layers[2].neurons; i++) {
189
+ const output = this.activations[2][i];
190
+ const target = this.expectedOutput[i];
191
+ // Derivative of loss with respect to output * derivative of sigmoid
192
+ outputDeltas.push((output - target) * output * (1 - output));
193
+ }
194
+
195
+ // Hidden to Output gradients
196
+ for (let i = 0; i < this.layers[1].neurons; i++) {
197
+ for (let j = 0; j < this.layers[2].neurons; j++) {
198
+ const weightIdx = i * this.layers[2].neurons + j;
199
+ this.gradients[1][weightIdx] = this.activations[1][i] * outputDeltas[j];
200
+ }
201
+ }
202
+
203
+ // Hidden layer deltas
204
+ const hiddenDeltas = Array(this.layers[1].neurons).fill(0);
205
+ for (let i = 0; i < this.layers[1].neurons; i++) {
206
+ let sum = 0;
207
+ for (let j = 0; j < this.layers[2].neurons; j++) {
208
+ const weightIdx = i * this.layers[2].neurons + j;
209
+ sum += this.weights[1][weightIdx] * outputDeltas[j];
210
+ }
211
+ // ReLU derivative is 1 if x > 0, otherwise 0
212
+ hiddenDeltas[i] = sum * (this.activations[1][i] > 0 ? 1 : 0);
213
+ }
214
+
215
+ // Input to Hidden gradients
216
+ for (let i = 0; i < this.layers[0].neurons; i++) {
217
+ for (let j = 0; j < this.layers[1].neurons; j++) {
218
+ const weightIdx = i * this.layers[1].neurons + j;
219
+ this.gradients[0][weightIdx] = this.activations[0][i] * hiddenDeltas[j];
220
+ }
221
+ }
222
+
223
+ return this.gradients;
224
+ }
225
+
226
+ // Update weights based on gradients
227
+ updateWeights(learningRate = 0.1) {
228
+ // Update weights using calculated gradients
229
+ for (let layerIdx = 0; layerIdx < this.weights.length; layerIdx++) {
230
+ for (let i = 0; i < this.weights[layerIdx].length; i++) {
231
+ this.weights[layerIdx][i] -= learningRate * this.gradients[layerIdx][i];
232
+ }
233
+ }
234
+
235
+ // Update biases (not shown in animation for simplicity)
236
+ // In a real implementation, we would update biases too
237
+ }
238
+ }
239
+
240
+ // Canvas resize functionality
241
+ function resizeCanvas() {
242
+ const container = canvas.parentElement;
243
+ canvas.width = container.clientWidth;
244
+ canvas.height = container.clientHeight;
245
+
246
+ // Redraw if already animating
247
+ if (animationState.network) {
248
+ drawNetwork(animationState.network);
249
+ }
250
+ }
251
+
252
+ // Initialize animation
253
+ function initAnimation() {
254
+ if (!canvas) return;
255
+
256
+ resizeCanvas();
257
+ window.addEventListener('resize', resizeCanvas);
258
+
259
+ // Create neural network
260
+ animationState.network = new NeuralNetwork();
261
+
262
+ // Draw initial state
263
+ drawNetwork(animationState.network);
264
+
265
+ // Update variables display
266
+ updateVariablesDisplay(animationState.network);
267
+
268
+ // Set button states
269
+ startButton.disabled = false;
270
+ pauseButton.disabled = true;
271
+ resetButton.disabled = true;
272
+ }
273
+
274
+ // Draw the neural network
275
+ function drawNetwork(network) {
276
+ if (!ctx) return;
277
+
278
+ // Clear canvas
279
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
280
+
281
+ const padding = 50;
282
+ const width = canvas.width - padding * 2;
283
+ const height = canvas.height - padding * 2;
284
+
285
+ // Calculate neuron positions
286
+ const layers = network.layers;
287
+ const layerPositions = [];
288
+
289
+ for (let i = 0; i < layers.length; i++) {
290
+ const layerNeurons = [];
291
+ const x = padding + (width / (layers.length - 1)) * i;
292
+
293
+ for (let j = 0; j < layers[i].neurons; j++) {
294
+ const y = padding + (height / (layers[i].neurons + 1)) * (j + 1);
295
+ layerNeurons.push({ x, y });
296
+ }
297
+
298
+ layerPositions.push(layerNeurons);
299
+ }
300
+
301
+ // Draw connections
302
+ for (let layerIdx = 0; layerIdx < layers.length - 1; layerIdx++) {
303
+ for (let i = 0; i < layers[layerIdx].neurons; i++) {
304
+ for (let j = 0; j < layers[layerIdx + 1].neurons; j++) {
305
+ const weightIdx = i * layers[layerIdx + 1].neurons + j;
306
+ const weight = network.weights[layerIdx][weightIdx];
307
+
308
+ // Map weight to opacity for visualization
309
+ const normalizedWeight = Math.min(Math.abs(weight) * 5, 1);
310
+
311
+ // Set connection style based on the animation step
312
+ let connectionColor = '#ccc';
313
+
314
+ if (animationState.currentStep === 1) {
315
+ // Forward pass: blue
316
+ connectionColor = `rgba(52, 152, 219, ${normalizedWeight})`;
317
+ } else if (animationState.currentStep === 2) {
318
+ // Error calculation: red
319
+ if (layerIdx === network.weights.length - 1) {
320
+ connectionColor = `rgba(231, 76, 60, ${normalizedWeight})`;
321
+ } else {
322
+ connectionColor = `rgba(52, 152, 219, ${normalizedWeight})`;
323
+ }
324
+ } else if (animationState.currentStep === 3) {
325
+ // Backward pass: purple
326
+ connectionColor = `rgba(155, 89, 182, ${normalizedWeight})`;
327
+ } else if (animationState.currentStep === 4) {
328
+ // Weight update: green
329
+ const gradientNormalized = Math.min(Math.abs(network.gradients[layerIdx][weightIdx]) * 20, 1);
330
+ connectionColor = `rgba(46, 204, 113, ${gradientNormalized})`;
331
+ } else {
332
+ // Default state: gray with weight intensity
333
+ connectionColor = `rgba(150, 150, 150, ${normalizedWeight})`;
334
+ }
335
+
336
+ // Draw the connection
337
+ ctx.beginPath();
338
+ ctx.moveTo(layerPositions[layerIdx][i].x, layerPositions[layerIdx][i].y);
339
+ ctx.lineTo(layerPositions[layerIdx + 1][j].x, layerPositions[layerIdx + 1][j].y);
340
+ ctx.strokeStyle = connectionColor;
341
+ ctx.lineWidth = 2;
342
+ ctx.stroke();
343
+ }
344
+ }
345
+ }
346
+
347
+ // Draw neurons
348
+ for (let layerIdx = 0; layerIdx < layers.length; layerIdx++) {
349
+ for (let i = 0; i < layers[layerIdx].neurons; i++) {
350
+ const { x, y } = layerPositions[layerIdx][i];
351
+
352
+ // Set neuron style based on activation value
353
+ const activation = network.activations[layerIdx][i];
354
+ const activationColor = `rgba(52, 152, 219, ${Math.min(Math.max(activation, 0.2), 0.9)})`;
355
+
356
+ // Draw neuron
357
+ ctx.beginPath();
358
+ ctx.arc(x, y, 20, 0, Math.PI * 2);
359
+ ctx.fillStyle = activationColor;
360
+ ctx.fill();
361
+ ctx.strokeStyle = '#2980b9';
362
+ ctx.lineWidth = 2;
363
+ ctx.stroke();
364
+
365
+ // Draw neuron value
366
+ ctx.fillStyle = '#fff';
367
+ ctx.font = '12px Arial';
368
+ ctx.textAlign = 'center';
369
+ ctx.textBaseline = 'middle';
370
+ ctx.fillText(activation.toFixed(2), x, y);
371
+
372
+ // Draw layer labels
373
+ if (i === 0) {
374
+ ctx.fillStyle = '#333';
375
+ ctx.font = '14px Arial';
376
+ ctx.textAlign = 'center';
377
+ ctx.fillText(layers[layerIdx].type.toUpperCase(), x, y - 40);
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ // Update the variables display
384
+ function updateVariablesDisplay(network) {
385
+ const variablesContainer = document.getElementById('variables-container');
386
+ if (!variablesContainer) return;
387
+
388
+ let html = '';
389
+
390
+ // Different display based on animation step
391
+ switch (animationState.currentStep) {
392
+ case 1: // Forward Pass
393
+ html += `<div class="variable">Input: [${network.activations[0].map(v => v.toFixed(2)).join(', ')}]</div>`;
394
+ html += `<div class="variable">Hidden: [${network.activations[1].map(v => v.toFixed(2)).join(', ')}]</div>`;
395
+ html += `<div class="variable">Output: [${network.activations[2].map(v => v.toFixed(2)).join(', ')}]</div>`;
396
+ break;
397
+ case 2: // Error Calculation
398
+ html += `<div class="variable">Prediction: [${network.activations[2].map(v => v.toFixed(2)).join(', ')}]</div>`;
399
+ html += `<div class="variable">Target: [${network.expectedOutput.join(', ')}]</div>`;
400
+ html += `<div class="variable">Error: ${network.error.toFixed(4)}</div>`;
401
+ break;
402
+ case 3: // Backward Pass
403
+ html += `<div class="variable">Output Deltas:</div>`;
404
+ for (let i = 0; i < network.layers[2].neurons; i++) {
405
+ const output = network.activations[2][i];
406
+ const target = network.expectedOutput[i];
407
+ const delta = (output - target) * output * (1 - output);
408
+ html += `<div class="variable"> δ${i}: ${delta.toFixed(4)}</div>`;
409
+ }
410
+ break;
411
+ case 4: // Weight Updates
412
+ html += `<div class="variable">Selected Gradients:</div>`;
413
+ // Show just a few example gradients to avoid clutter
414
+ for (let layerIdx = 0; layerIdx < network.gradients.length; layerIdx++) {
415
+ const layerName = layerIdx === 0 ? 'Input→Hidden' : 'Hidden→Output';
416
+ html += `<div class="variable">${layerName}:</div>`;
417
+
418
+ // Show first few gradients as examples
419
+ for (let i = 0; i < Math.min(3, network.gradients[layerIdx].length); i++) {
420
+ html += `<div class="variable"> ∇w${i}: ${network.gradients[layerIdx][i].toFixed(4)}</div>`;
421
+ }
422
+ }
423
+ break;
424
+ default:
425
+ html += `<div class="variable">Click "Start Animation" to begin</div>`;
426
+ }
427
+
428
+ variablesContainer.innerHTML = html;
429
+ }
430
+
431
+ // Animation steps
432
+ const animationSteps = [
433
+ {
434
+ name: 'Starting',
435
+ description: 'Neural network in initial state. Click "Start Animation" to begin.'
436
+ },
437
+ {
438
+ name: 'Forward Pass',
439
+ description: 'Input data flows through the network to produce a prediction. Each neuron computes a weighted sum of its inputs, then applies an activation function.'
440
+ },
441
+ {
442
+ name: 'Error Calculation',
443
+ description: 'The network compares its prediction with the expected output to compute the error. This error measures how far off the prediction is.'
444
+ },
445
+ {
446
+ name: 'Backward Pass',
447
+ description: 'The error is propagated backward through the network, assigning responsibility to each weight for the prediction error.'
448
+ },
449
+ {
450
+ name: 'Weight Updates',
451
+ description: 'Weights are adjusted in proportion to their contribution to the error. Weights that contributed more to the error are adjusted more significantly.'
452
+ }
453
+ ];
454
+
455
+ // Update step information display
456
+ function updateStepInfo(stepIndex) {
457
+ const stepName = document.getElementById('step-name');
458
+ const stepDescription = document.getElementById('step-description');
459
+
460
+ if (stepName && stepDescription && animationSteps[stepIndex]) {
461
+ stepName.textContent = animationSteps[stepIndex].name;
462
+ stepDescription.textContent = animationSteps[stepIndex].description;
463
+ }
464
+ }
465
+
466
+ // Animation loop
467
+ function animate(timestamp) {
468
+ if (!animationState.running) return;
469
+
470
+ // Calculate delta time for animation speed
471
+ const deltaTime = timestamp - animationState.lastTimestamp;
472
+ const interval = 3000 / animationState.speed; // Base interval divided by speed
473
+
474
+ if (deltaTime > interval || animationState.lastTimestamp === 0) {
475
+ animationState.lastTimestamp = timestamp;
476
+
477
+ // Progress through animation steps
478
+ if (animationState.currentStep === 0) {
479
+ // Initial state to forward pass
480
+ animationState.currentStep = 1;
481
+ animationState.network.forwardPass();
482
+ } else if (animationState.currentStep === 1) {
483
+ // Forward pass to error calculation
484
+ animationState.currentStep = 2;
485
+ } else if (animationState.currentStep === 2) {
486
+ // Error calculation to backward pass
487
+ animationState.currentStep = 3;
488
+ animationState.network.calculateGradients();
489
+ } else if (animationState.currentStep === 3) {
490
+ // Backward pass to weight updates
491
+ animationState.currentStep = 4;
492
+ } else if (animationState.currentStep === 4) {
493
+ // Weight updates to new forward pass
494
+ animationState.network.updateWeights(0.1);
495
+ animationState.currentStep = 1;
496
+ animationState.network.forwardPass();
497
+ }
498
+
499
+ // Update visuals
500
+ drawNetwork(animationState.network);
501
+ updateVariablesDisplay(animationState.network);
502
+ updateStepInfo(animationState.currentStep);
503
+ }
504
+
505
+ // Continue animation
506
+ animationState.animationFrameId = requestAnimationFrame(animate);
507
+ }
508
+
509
+ // Start animation
510
+ function startAnimation() {
511
+ if (!animationState.running) {
512
+ animationState.running = true;
513
+ animationState.lastTimestamp = 0;
514
+ animationState.animationFrameId = requestAnimationFrame(animate);
515
+
516
+ startButton.disabled = true;
517
+ pauseButton.disabled = false;
518
+ resetButton.disabled = false;
519
+ }
520
+ }
521
+
522
+ // Pause animation
523
+ function pauseAnimation() {
524
+ if (animationState.running) {
525
+ animationState.running = false;
526
+ if (animationState.animationFrameId) {
527
+ cancelAnimationFrame(animationState.animationFrameId);
528
+ }
529
+
530
+ startButton.disabled = false;
531
+ pauseButton.disabled = true;
532
+ resetButton.disabled = false;
533
+ }
534
+ }
535
+
536
+ // Reset animation
537
+ function resetAnimation() {
538
+ pauseAnimation();
539
+
540
+ animationState.currentStep = 0;
541
+ animationState.network = new NeuralNetwork();
542
+
543
+ drawNetwork(animationState.network);
544
+ updateVariablesDisplay(animationState.network);
545
+ updateStepInfo(animationState.currentStep);
546
+
547
+ startButton.disabled = false;
548
+ pauseButton.disabled = true;
549
+ resetButton.disabled = true;
550
+ }
551
+
552
+ // Control event listeners
553
+ startButton.addEventListener('click', startAnimation);
554
+ pauseButton.addEventListener('click', pauseAnimation);
555
+ resetButton.addEventListener('click', resetAnimation);
556
+
557
+ speedControl.addEventListener('input', () => {
558
+ animationState.speed = parseInt(speedControl.value, 10);
559
+ });
560
+
561
+ // Initialize the animation
562
+ initAnimation();
563
+ });
js/check-drag-drop.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Check and initialize drag-drop functionality
2
+ document.addEventListener('DOMContentLoaded', function() {
3
+ // Check if the initializeDragAndDrop function exists
4
+ if (typeof initializeDragAndDrop === 'function') {
5
+ console.log('Drag and Drop initialization found!');
6
+
7
+ // Delay initialization to ensure everything is loaded
8
+ setTimeout(() => {
9
+ console.log('Initializing Drag and Drop functionality...');
10
+ // Call the initialization function
11
+ initializeDragAndDrop();
12
+ }, 500);
13
+ } else {
14
+ console.error('ERROR: Drag and Drop initialization function not found!');
15
+ console.log('Make sure drag-drop.js is correctly loaded before other scripts.');
16
+ }
17
+ });
js/complete-drag-fix.js ADDED
@@ -0,0 +1,1356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Complete drag and drop fix for neural network playground
2
+ // This handles both initial node creation and moving existing nodes
3
+
4
+ (function() {
5
+ console.log('Loading complete drag and drop fix...');
6
+
7
+ document.addEventListener('DOMContentLoaded', function() {
8
+ // Wait a bit to ensure other scripts have loaded
9
+ setTimeout(initializeCompleteDragFix, 1000);
10
+ });
11
+
12
+ function initializeCompleteDragFix() {
13
+ console.log('Initializing complete drag and drop fix');
14
+
15
+ // Get necessary elements
16
+ const canvas = document.getElementById('network-canvas');
17
+ const nodeItems = document.querySelectorAll('.node-item');
18
+
19
+ if (!canvas) {
20
+ console.error('Canvas element not found!');
21
+ return;
22
+ }
23
+
24
+ // Track state for moving existing nodes
25
+ let activeNode = null;
26
+ let offsetX = 0;
27
+ let offsetY = 0;
28
+ let isDragging = false;
29
+
30
+ // Track node counts for naming
31
+ const nodeCounter = {};
32
+
33
+ // Anti-duplication system for new nodes
34
+ const recentlyCreated = {
35
+ nodeIds: new Set(),
36
+ timestamp: 0
37
+ };
38
+
39
+ // Network model structure (reused from original code)
40
+ let networkLayers = {
41
+ layers: [],
42
+ connections: []
43
+ };
44
+
45
+ // Helper function for formatting numbers
46
+ function formatNumber(num) {
47
+ if (num === 0) return '0';
48
+ if (!num) return 'N/A';
49
+
50
+ if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
51
+ if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
52
+ if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
53
+ return num.toString();
54
+ }
55
+
56
+ // Add debug button for Conv2D parameters
57
+ addConv2DFixButton();
58
+
59
+ // 1. DRAGGING NEW NODES FROM PANEL TO CANVAS
60
+
61
+ // Setup draggable items
62
+ nodeItems.forEach(item => {
63
+ // Override existing dragstart handler for reliability
64
+ item.addEventListener('dragstart', function(e) {
65
+ const nodeType = this.getAttribute('data-type');
66
+ console.log(`Starting drag for new ${nodeType} node`);
67
+
68
+ // Ensure data is properly set for transfer
69
+ e.dataTransfer.setData('text/plain', nodeType);
70
+ e.dataTransfer.effectAllowed = 'copy';
71
+
72
+ // Create ghost image
73
+ const ghost = this.cloneNode(true);
74
+ ghost.style.opacity = '0.5';
75
+ document.body.appendChild(ghost);
76
+ e.dataTransfer.setDragImage(ghost, 0, 0);
77
+
78
+ // Remove ghost image after dragstart completes
79
+ setTimeout(() => {
80
+ document.body.removeChild(ghost);
81
+ }, 0);
82
+ });
83
+ });
84
+
85
+ // Add canvas event handlers for dropping new nodes
86
+ function handleDragOver(e) {
87
+ e.preventDefault();
88
+ e.dataTransfer.dropEffect = 'copy';
89
+ }
90
+
91
+ // Remove old handlers first to prevent duplicates
92
+ canvas.removeEventListener('dragover', handleDragOver);
93
+ canvas.addEventListener('dragover', handleDragOver);
94
+
95
+ // Create drop handler for new nodes
96
+ function handleDrop(e) {
97
+ e.preventDefault();
98
+ console.log('Drop event triggered');
99
+
100
+ // Debounce: prevent multiple drops in quick succession
101
+ const now = Date.now();
102
+ if (now - recentlyCreated.timestamp < 500) {
103
+ console.log('Debouncing drop event');
104
+ return;
105
+ }
106
+ recentlyCreated.timestamp = now;
107
+
108
+ // Get node type from dataTransfer
109
+ const nodeType = e.dataTransfer.getData('text/plain');
110
+ if (!nodeType) {
111
+ console.error('No node type found in drop data');
112
+ return;
113
+ }
114
+
115
+ console.log(`Creating new ${nodeType} node`);
116
+
117
+ // Calculate position for new node
118
+ const canvasRect = canvas.getBoundingClientRect();
119
+ const x = e.clientX - canvasRect.left - 75;
120
+ const y = e.clientY - canvasRect.top - 30;
121
+
122
+ // Ensure position is within canvas
123
+ const posX = Math.max(0, Math.min(canvasRect.width - 150, x));
124
+ const posY = Math.max(0, Math.min(canvasRect.height - 100, y));
125
+
126
+ // Generate unique ID
127
+ const layerId = `${nodeType}-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
128
+
129
+ // Create the new node
130
+ createNode(nodeType, layerId, posX, posY);
131
+ }
132
+
133
+ // Remove old handler first to prevent duplicates
134
+ canvas.removeEventListener('drop', handleDrop);
135
+ canvas.addEventListener('drop', handleDrop);
136
+
137
+ // Function to create a new node
138
+ function createNode(nodeType, layerId, posX, posY) {
139
+ // Increment counter for this node type
140
+ nodeCounter[nodeType] = (nodeCounter[nodeType] || 0) + 1;
141
+
142
+ // Get default configuration from neural network module or use our own defaults
143
+ let nodeConfig;
144
+ if (window.neuralNetwork && window.neuralNetwork.createNodeConfig) {
145
+ nodeConfig = window.neuralNetwork.createNodeConfig(nodeType);
146
+ } else {
147
+ // Fallback default configs if neural network module is not available
148
+ nodeConfig = {};
149
+ switch (nodeType) {
150
+ case 'input':
151
+ nodeConfig = {
152
+ shape: [28, 28, 1],
153
+ outputShape: [28, 28, 1],
154
+ parameters: 0
155
+ };
156
+ break;
157
+
158
+ case 'hidden':
159
+ nodeConfig = {
160
+ units: 128,
161
+ activation: 'relu',
162
+ outputShape: [128],
163
+ parameters: 0
164
+ };
165
+ break;
166
+
167
+ case 'output':
168
+ nodeConfig = {
169
+ units: 10,
170
+ activation: 'softmax',
171
+ outputShape: [10],
172
+ parameters: 0
173
+ };
174
+ break;
175
+
176
+ case 'conv':
177
+ nodeConfig = {
178
+ filters: 32,
179
+ kernelSize: [3, 3],
180
+ strides: [1, 1],
181
+ padding: 'same',
182
+ activation: 'relu',
183
+ outputShape: ['?', '?', 32],
184
+ parameters: 0
185
+ };
186
+ break;
187
+
188
+ case 'pool':
189
+ nodeConfig = {
190
+ poolSize: [2, 2],
191
+ strides: [2, 2],
192
+ padding: 'valid',
193
+ poolType: 'max',
194
+ outputShape: ['?', '?', '?'],
195
+ parameters: 0
196
+ };
197
+ break;
198
+
199
+ case 'linear':
200
+ nodeConfig = {
201
+ units: 64,
202
+ activation: 'relu',
203
+ useBias: true,
204
+ outputShape: [64],
205
+ parameters: 0
206
+ };
207
+ break;
208
+ }
209
+ }
210
+
211
+ // Ensure Conv2D has properly formatted array values
212
+ if (nodeType === 'conv') {
213
+ if (!nodeConfig.kernelSize || typeof nodeConfig.kernelSize === 'string') {
214
+ nodeConfig.kernelSize = [3, 3];
215
+ }
216
+ if (!nodeConfig.strides || typeof nodeConfig.strides === 'string') {
217
+ nodeConfig.strides = [1, 1];
218
+ }
219
+ if (!nodeConfig.filters || isNaN(nodeConfig.filters)) {
220
+ nodeConfig.filters = 32;
221
+ }
222
+ nodeConfig.padding = nodeConfig.padding || 'same';
223
+ nodeConfig.activation = nodeConfig.activation || 'relu';
224
+ }
225
+
226
+ // Create node element
227
+ const canvasNode = document.createElement('div');
228
+ canvasNode.className = `canvas-node ${nodeType}-node`;
229
+ canvasNode.setAttribute('data-type', nodeType);
230
+ canvasNode.setAttribute('data-id', layerId);
231
+ canvasNode.style.position = 'absolute';
232
+ canvasNode.style.left = `${posX}px`;
233
+ canvasNode.style.top = `${posY}px`;
234
+
235
+ // Set up node content (input/output shape, parameters)
236
+ let nodeName, inputShape, outputShape, parameters;
237
+
238
+ switch(nodeType) {
239
+ case 'input':
240
+ nodeName = 'Input Layer';
241
+ inputShape = 'N/A';
242
+ outputShape = '[' + nodeConfig.shape.join(' × ') + ']';
243
+ parameters = nodeConfig.parameters;
244
+ break;
245
+ case 'hidden':
246
+ nodeConfig.units = nodeCounter[nodeType] === 1 ? 128 : 64;
247
+ nodeName = `Hidden Layer ${nodeCounter[nodeType]}`;
248
+ inputShape = 'Connect input';
249
+ outputShape = `[${nodeConfig.units}]`;
250
+ parameters = 'Connect input to calculate';
251
+ break;
252
+ case 'output':
253
+ nodeName = 'Output Layer';
254
+ inputShape = 'Connect input';
255
+ outputShape = `[${nodeConfig.units}]`;
256
+ parameters = 'Connect input to calculate';
257
+ break;
258
+ case 'conv':
259
+ nodeConfig.filters = 32 * nodeCounter[nodeType];
260
+ nodeName = `Conv2D ${nodeCounter[nodeType]}`;
261
+ inputShape = 'Connect input';
262
+ outputShape = 'Depends on input';
263
+ parameters = `Kernel: ${nodeConfig.kernelSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`;
264
+ break;
265
+ case 'pool':
266
+ nodeName = `Pooling ${nodeCounter[nodeType]}`;
267
+ inputShape = 'Connect input';
268
+ outputShape = 'Depends on input';
269
+ parameters = `Pool size: ${nodeConfig.poolSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`;
270
+ break;
271
+ default:
272
+ nodeName = 'Unknown Layer';
273
+ inputShape = 'N/A';
274
+ outputShape = 'N/A';
275
+ parameters = 'N/A';
276
+ }
277
+
278
+ // Create node content
279
+ const nodeContent = document.createElement('div');
280
+ nodeContent.className = 'node-content';
281
+
282
+ // Add shape information
283
+ const shapeInfo = document.createElement('div');
284
+ shapeInfo.className = 'shape-info';
285
+ shapeInfo.innerHTML = `
286
+ <div class="shape-row"><span class="shape-label">Input:</span> <span class="input-shape">${inputShape}</span></div>
287
+ <div class="shape-row"><span class="shape-label">Output:</span> <span class="output-shape">${outputShape}</span></div>
288
+ `;
289
+
290
+ // Add parameters section
291
+ const paramsSection = document.createElement('div');
292
+ paramsSection.className = 'params-section';
293
+ paramsSection.innerHTML = `
294
+ <div class="params-details">${parameters}</div>
295
+ <div class="node-parameters">Params: ${nodeConfig.parameters !== undefined ? formatNumber(nodeConfig.parameters) : '?'}</div>
296
+ `;
297
+
298
+ // Assemble content
299
+ nodeContent.appendChild(shapeInfo);
300
+ nodeContent.appendChild(paramsSection);
301
+
302
+ // Add dimensions section
303
+ const dimensionsSection = document.createElement('div');
304
+ dimensionsSection.className = 'node-dimensions';
305
+
306
+ // Set dimensions text based on node type
307
+ let dimensionsText = '';
308
+ switch(nodeType) {
309
+ case 'input':
310
+ dimensionsText = nodeConfig.shape.join(' × ');
311
+ break;
312
+ case 'hidden':
313
+ case 'output':
314
+ dimensionsText = nodeConfig.units.toString();
315
+ break;
316
+ case 'conv':
317
+ if (nodeConfig.inputShape && nodeConfig.outputShape) {
318
+ dimensionsText = `${nodeConfig.inputShape.join('×')} → ${nodeConfig.outputShape.join('×')}`;
319
+ } else {
320
+ dimensionsText = `? → ${nodeConfig.filters} filters`;
321
+ }
322
+ break;
323
+ case 'pool':
324
+ if (nodeConfig.inputShape && nodeConfig.outputShape) {
325
+ dimensionsText = `${nodeConfig.inputShape.join('×')} → ${nodeConfig.outputShape.join('×')}`;
326
+ } else {
327
+ dimensionsText = `? → ?`;
328
+ }
329
+ break;
330
+ }
331
+ dimensionsSection.textContent = dimensionsText;
332
+
333
+ // Create node title
334
+ const nodeTitle = document.createElement('div');
335
+ nodeTitle.className = 'node-title';
336
+ nodeTitle.textContent = nodeName;
337
+
338
+ // Add node controls (edit and delete buttons)
339
+ const nodeControls = document.createElement('div');
340
+ nodeControls.className = 'node-controls';
341
+
342
+ const editButton = document.createElement('button');
343
+ editButton.className = 'node-edit-btn';
344
+ editButton.innerHTML = '✎';
345
+ editButton.title = 'Edit Layer';
346
+
347
+ const deleteButton = document.createElement('button');
348
+ deleteButton.className = 'node-delete-btn';
349
+ deleteButton.innerHTML = '×';
350
+ deleteButton.title = 'Delete Layer';
351
+
352
+ nodeControls.appendChild(editButton);
353
+ nodeControls.appendChild(deleteButton);
354
+
355
+ // Add connection ports
356
+ const portIn = document.createElement('div');
357
+ portIn.className = 'node-port port-in';
358
+
359
+ const portOut = document.createElement('div');
360
+ portOut.className = 'node-port port-out';
361
+
362
+ // Assemble the node
363
+ canvasNode.appendChild(nodeTitle);
364
+ canvasNode.appendChild(nodeControls);
365
+ canvasNode.appendChild(dimensionsSection);
366
+ canvasNode.appendChild(nodeContent);
367
+ canvasNode.appendChild(portIn);
368
+ canvasNode.appendChild(portOut);
369
+
370
+ // Store metadata
371
+ canvasNode.setAttribute('data-name', nodeName);
372
+ canvasNode.setAttribute('data-dimensions', dimensionsText);
373
+ canvasNode.layerConfig = nodeConfig;
374
+
375
+ // Add node to canvas
376
+ canvas.appendChild(canvasNode);
377
+
378
+ // Add to network model
379
+ networkLayers.layers.push({
380
+ id: layerId,
381
+ type: nodeType,
382
+ name: nodeName,
383
+ position: { x: posX, y: posY },
384
+ dimensions: dimensionsText,
385
+ config: nodeConfig,
386
+ parameters: nodeConfig.parameters || 0
387
+ });
388
+
389
+ // Set up event handlers (edit, delete, connections)
390
+ setupNodeEventHandlers(canvasNode);
391
+
392
+ // Hide canvas hint
393
+ const canvasHint = document.querySelector('.canvas-hint');
394
+ if (canvasHint) {
395
+ canvasHint.style.display = 'none';
396
+ }
397
+
398
+ // Notify model update
399
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
400
+ detail: networkLayers
401
+ }));
402
+
403
+ console.log(`Node created: ${nodeType} (${layerId})`);
404
+ return canvasNode;
405
+ }
406
+
407
+ // 2. MOVING EXISTING NODES ON CANVAS
408
+
409
+ // Setup event handlers for node actions
410
+ function setupNodeEventHandlers(node) {
411
+ // Setup direct mouse handlers for dragging
412
+ node.addEventListener('mousedown', function(e) {
413
+ // Skip if clicking on controls or ports
414
+ if (e.target.closest('.node-controls') || e.target.closest('.node-port')) {
415
+ return;
416
+ }
417
+
418
+ console.log(`Mouse down on node: ${node.getAttribute('data-id')}`);
419
+
420
+ // Initialize drag
421
+ activeNode = node;
422
+ const rect = node.getBoundingClientRect();
423
+ offsetX = e.clientX - rect.left;
424
+ offsetY = e.clientY - rect.top;
425
+ isDragging = true;
426
+
427
+ // Visual indication
428
+ node.classList.add('dragging');
429
+ document.body.classList.add('node-dragging');
430
+ node.style.zIndex = '1000';
431
+
432
+ e.preventDefault();
433
+ });
434
+
435
+ // Edit button click
436
+ const editButton = node.querySelector('.node-edit-btn');
437
+ if (editButton) {
438
+ editButton.addEventListener('click', function(e) {
439
+ e.stopPropagation();
440
+ openLayerEditor(node);
441
+ });
442
+ }
443
+
444
+ // Delete button click
445
+ const deleteButton = node.querySelector('.node-delete-btn');
446
+ if (deleteButton) {
447
+ deleteButton.addEventListener('click', function(e) {
448
+ e.stopPropagation();
449
+ deleteNode(node);
450
+ });
451
+ }
452
+
453
+ // Double-click to edit
454
+ node.addEventListener('dblclick', function() {
455
+ openLayerEditor(node);
456
+ });
457
+
458
+ // Right-click to delete
459
+ node.addEventListener('contextmenu', function(e) {
460
+ e.preventDefault();
461
+ deleteNode(node);
462
+ });
463
+
464
+ // Connection port events
465
+ const portOut = node.querySelector('.port-out');
466
+ if (portOut) {
467
+ portOut.addEventListener('mousedown', function(e) {
468
+ e.stopPropagation();
469
+ // Use our own connection handler instead of relying on window.startConnection
470
+ startConnectionHandler(node, e);
471
+ });
472
+ }
473
+ }
474
+
475
+ // Global mouse handlers for dragging
476
+ document.addEventListener('mousemove', function(e) {
477
+ if (!isDragging || !activeNode) return;
478
+
479
+ // Log occasionally for debugging
480
+ if (Math.random() < 0.05) {
481
+ console.log('Node is being dragged...');
482
+ }
483
+
484
+ const canvasRect = canvas.getBoundingClientRect();
485
+ let x = e.clientX - canvasRect.left - offsetX;
486
+ let y = e.clientY - canvasRect.top - offsetY;
487
+
488
+ // Keep within canvas
489
+ const nodeWidth = activeNode.offsetWidth || 180;
490
+ const nodeHeight = activeNode.offsetHeight || 120;
491
+
492
+ x = Math.max(0, Math.min(canvasRect.width - nodeWidth, x));
493
+ y = Math.max(0, Math.min(canvasRect.height - nodeHeight, y));
494
+
495
+ // Move node
496
+ activeNode.style.left = `${x}px`;
497
+ activeNode.style.top = `${y}px`;
498
+
499
+ // Update model
500
+ const nodeId = activeNode.getAttribute('data-id');
501
+ const layerIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId);
502
+ if (layerIndex !== -1) {
503
+ networkLayers.layers[layerIndex].position = { x, y };
504
+ }
505
+
506
+ // Update connections
507
+ updateConnections(nodeId);
508
+ });
509
+
510
+ document.addEventListener('mouseup', function() {
511
+ if (!isDragging || !activeNode) return;
512
+
513
+ console.log('Node drag complete');
514
+
515
+ // Visual cleanup
516
+ activeNode.classList.remove('dragging');
517
+ document.body.classList.remove('node-dragging');
518
+ activeNode.style.zIndex = '10';
519
+
520
+ // Final connection update
521
+ updateConnections();
522
+
523
+ // Cleanup
524
+ isDragging = false;
525
+ activeNode = null;
526
+
527
+ // Notify model update
528
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
529
+ detail: networkLayers
530
+ }));
531
+ });
532
+
533
+ // Add handlers to existing nodes (for page refresh cases)
534
+ document.querySelectorAll('.canvas-node').forEach(setupNodeEventHandlers);
535
+
536
+ // 3. SUPPORTING FUNCTIONS
537
+
538
+ // Delete a node
539
+ function deleteNode(node) {
540
+ if (!node) return;
541
+
542
+ const nodeId = node.getAttribute('data-id');
543
+ console.log(`Deleting node: ${nodeId}`);
544
+
545
+ // Remove connections
546
+ const connections = document.querySelectorAll(`.connection[data-source="${nodeId}"], .connection[data-target="${nodeId}"]`);
547
+ connections.forEach(conn => {
548
+ if (conn.parentNode) {
549
+ conn.parentNode.removeChild(conn);
550
+ }
551
+ });
552
+
553
+ // Update model
554
+ networkLayers.connections = networkLayers.connections.filter(conn =>
555
+ conn.source !== nodeId && conn.target !== nodeId
556
+ );
557
+
558
+ const layerIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId);
559
+ if (layerIndex !== -1) {
560
+ networkLayers.layers.splice(layerIndex, 1);
561
+ }
562
+
563
+ // Remove from DOM
564
+ if (node.parentNode) {
565
+ node.parentNode.removeChild(node);
566
+ }
567
+
568
+ // Show hint if no nodes left
569
+ if (document.querySelectorAll('.canvas-node').length === 0) {
570
+ const canvasHint = document.querySelector('.canvas-hint');
571
+ if (canvasHint) {
572
+ canvasHint.style.display = 'block';
573
+ }
574
+ }
575
+
576
+ // Notify model update
577
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
578
+ detail: networkLayers
579
+ }));
580
+ }
581
+
582
+ // Open layer editor
583
+ function openLayerEditor(node) {
584
+ if (!node) return;
585
+
586
+ const nodeId = node.getAttribute('data-id');
587
+ const nodeType = node.getAttribute('data-type');
588
+ const nodeName = node.getAttribute('data-name');
589
+ const dimensions = node.getAttribute('data-dimensions');
590
+
591
+ console.log(`Opening editor for node: ${nodeId}`);
592
+
593
+ // Trigger editor event
594
+ document.dispatchEvent(new CustomEvent('openLayerEditor', {
595
+ detail: {
596
+ id: nodeId,
597
+ type: nodeType,
598
+ name: nodeName,
599
+ dimensions: dimensions,
600
+ node: node
601
+ }
602
+ }));
603
+ }
604
+
605
+ // Update connections
606
+ function updateConnections(specificNodeId = null) {
607
+ // Get connections to update
608
+ let connections;
609
+ if (specificNodeId) {
610
+ connections = document.querySelectorAll(`.connection[data-source="${specificNodeId}"], .connection[data-target="${specificNodeId}"]`);
611
+ } else {
612
+ connections = document.querySelectorAll('.connection:not(.temp-connection)');
613
+ }
614
+
615
+ connections.forEach(connection => {
616
+ const sourceId = connection.getAttribute('data-source');
617
+ const targetId = connection.getAttribute('data-target');
618
+
619
+ const sourceNode = document.querySelector(`.canvas-node[data-id="${sourceId}"]`);
620
+ const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`);
621
+
622
+ if (sourceNode && targetNode) {
623
+ const sourcePort = sourceNode.querySelector('.port-out');
624
+ const targetPort = targetNode.querySelector('.port-in');
625
+
626
+ if (sourcePort && targetPort) {
627
+ const canvasRect = canvas.getBoundingClientRect();
628
+ const sourceRect = sourcePort.getBoundingClientRect();
629
+ const targetRect = targetPort.getBoundingClientRect();
630
+
631
+ const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left;
632
+ const startY = sourceRect.top + sourceRect.height / 2 - canvasRect.top;
633
+ const endX = targetRect.left + targetRect.width / 2 - canvasRect.left;
634
+ const endY = targetRect.top + targetRect.height / 2 - canvasRect.top;
635
+
636
+ const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
637
+ const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
638
+
639
+ connection.style.left = `${startX}px`;
640
+ connection.style.top = `${startY}px`;
641
+ connection.style.width = `${length}px`;
642
+ connection.style.transform = `rotate(${angle}deg)`;
643
+ }
644
+ } else {
645
+ // Remove orphaned connection
646
+ if (connection.parentNode) {
647
+ connection.parentNode.removeChild(connection);
648
+ }
649
+ }
650
+ });
651
+ }
652
+
653
+ // 5. CONNECTION HANDLING
654
+
655
+ // Connection state tracking
656
+ let tempConnection = null;
657
+ let connectionSource = null;
658
+
659
+ // Start creating a connection
660
+ function startConnectionHandler(sourceNode, event) {
661
+ console.log('Starting connection from node:', sourceNode.getAttribute('data-id'));
662
+
663
+ // Cancel any existing connection attempt
664
+ if (tempConnection && tempConnection.parentNode) {
665
+ tempConnection.parentNode.removeChild(tempConnection);
666
+ }
667
+
668
+ // Create a temporary connection element
669
+ tempConnection = document.createElement('div');
670
+ tempConnection.className = 'connection temp-connection';
671
+ canvas.appendChild(tempConnection);
672
+
673
+ // Store the source node
674
+ connectionSource = sourceNode;
675
+
676
+ // Get initial positions
677
+ const sourceId = sourceNode.getAttribute('data-id');
678
+ const sourcePort = sourceNode.querySelector('.port-out');
679
+ const canvasRect = canvas.getBoundingClientRect();
680
+ const sourceRect = sourcePort.getBoundingClientRect();
681
+ const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left;
682
+ const startY = sourceRect.top + sourceRect.height / 2 - canvasRect.top;
683
+
684
+ // Set initial position
685
+ tempConnection.style.left = `${startX}px`;
686
+ tempConnection.style.top = `${startY}px`;
687
+ tempConnection.setAttribute('data-source', sourceId);
688
+
689
+ // Add event listeners for moving and completing the connection
690
+ document.addEventListener('mousemove', moveConnectionHandler);
691
+ document.addEventListener('mouseup', endConnectionHandler);
692
+
693
+ event.preventDefault();
694
+ event.stopPropagation();
695
+ }
696
+
697
+ // Update the temporary connection during drag
698
+ function moveConnectionHandler(event) {
699
+ if (!tempConnection || !connectionSource) return;
700
+
701
+ const canvasRect = canvas.getBoundingClientRect();
702
+ const sourcePort = connectionSource.querySelector('.port-out');
703
+ const sourceRect = sourcePort.getBoundingClientRect();
704
+
705
+ const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left;
706
+ const startY = sourceRect.top + sourceRect.height / 2 - canvasRect.top;
707
+ const endX = event.clientX - canvasRect.left;
708
+ const endY = event.clientY - canvasRect.top;
709
+
710
+ const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
711
+ const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
712
+
713
+ tempConnection.style.left = `${startX}px`;
714
+ tempConnection.style.top = `${startY}px`;
715
+ tempConnection.style.width = `${length}px`;
716
+ tempConnection.style.transform = `rotate(${angle}deg)`;
717
+ }
718
+
719
+ // Complete or cancel the connection
720
+ function endConnectionHandler(event) {
721
+ // Remove the event listeners
722
+ document.removeEventListener('mousemove', moveConnectionHandler);
723
+ document.removeEventListener('mouseup', endConnectionHandler);
724
+
725
+ if (!tempConnection || !connectionSource) return;
726
+
727
+ // Check if we're over a valid target (port-in)
728
+ const targetPort = document.elementFromPoint(event.clientX, event.clientY);
729
+ let targetNode = null;
730
+
731
+ if (targetPort && targetPort.classList.contains('port-in')) {
732
+ targetNode = targetPort.closest('.canvas-node');
733
+ }
734
+
735
+ if (targetNode) {
736
+ const sourceId = connectionSource.getAttribute('data-id');
737
+ const targetId = targetNode.getAttribute('data-id');
738
+
739
+ // Prevent self-connections
740
+ if (sourceId === targetId) {
741
+ console.log('Cannot connect a node to itself');
742
+ if (tempConnection.parentNode) {
743
+ tempConnection.parentNode.removeChild(tempConnection);
744
+ }
745
+ tempConnection = null;
746
+ connectionSource = null;
747
+ return;
748
+ }
749
+
750
+ // Check if connection already exists
751
+ const existingConnection = document.querySelector(`.connection[data-source="${sourceId}"][data-target="${targetId}"]`);
752
+ if (existingConnection) {
753
+ console.log('Connection already exists');
754
+ if (tempConnection.parentNode) {
755
+ tempConnection.parentNode.removeChild(tempConnection);
756
+ }
757
+ tempConnection = null;
758
+ connectionSource = null;
759
+ return;
760
+ }
761
+
762
+ console.log(`Creating connection: ${sourceId} → ${targetId}`);
763
+
764
+ // Create the permanent connection
765
+ tempConnection.classList.remove('temp-connection');
766
+ tempConnection.setAttribute('data-target', targetId);
767
+
768
+ // Add to network model
769
+ networkLayers.connections.push({
770
+ source: sourceId,
771
+ target: targetId
772
+ });
773
+
774
+ // Update connection display
775
+ updateConnections();
776
+
777
+ // Update parameters based on the new connection
778
+ updateParametersAfterConnection(sourceId, targetId);
779
+
780
+ // Notify model update
781
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
782
+ detail: networkLayers
783
+ }));
784
+ } else {
785
+ // No valid target, remove the temp connection
786
+ if (tempConnection.parentNode) {
787
+ tempConnection.parentNode.removeChild(tempConnection);
788
+ }
789
+ }
790
+
791
+ // Reset state
792
+ tempConnection = null;
793
+ connectionSource = null;
794
+ }
795
+
796
+ // Update parameters after a connection is made
797
+ function updateParametersAfterConnection(sourceId, targetId) {
798
+ const sourceNode = document.querySelector(`.canvas-node[data-id="${sourceId}"]`);
799
+ const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`);
800
+
801
+ if (!sourceNode || !targetNode) return;
802
+
803
+ const sourceType = sourceNode.getAttribute('data-type');
804
+ const targetType = targetNode.getAttribute('data-type');
805
+
806
+ const sourceConfig = sourceNode.layerConfig || {};
807
+ const targetConfig = targetNode.layerConfig || {};
808
+
809
+ console.log(`Updating parameters: ${sourceType} → ${targetType}`);
810
+
811
+ // Check if target has a manual output shape (user set)
812
+ const hasManualOutputShape = targetConfig.outputShape &&
813
+ Array.isArray(targetConfig.outputShape) &&
814
+ targetConfig.outputShape.length > 0 &&
815
+ targetConfig.outputShape.some(dim => dim !== '?' && dim !== '');
816
+
817
+ console.log(`Target has manual output shape: ${hasManualOutputShape}`, targetConfig.outputShape);
818
+
819
+ // Set input shape of target based on output shape of source
820
+ if (sourceConfig.outputShape) {
821
+ targetConfig.inputShape = [...sourceConfig.outputShape];
822
+
823
+ // Update the display
824
+ const inputShapeDisplay = targetNode.querySelector('.input-shape');
825
+ if (inputShapeDisplay) {
826
+ inputShapeDisplay.textContent = `[${sourceConfig.outputShape.join(' × ')}]`;
827
+ }
828
+ }
829
+
830
+ // If target has a manual output shape, don't recalculate the output shape
831
+ if (hasManualOutputShape) {
832
+ console.log('Preserving manual output shape:', targetConfig.outputShape);
833
+ } else {
834
+ // Calculate output shape and parameters based on node type
835
+ if (window.neuralNetwork && window.neuralNetwork.calculateOutputShape) {
836
+ // Use neural network module if available
837
+ const outputShape = window.neuralNetwork.calculateOutputShape(targetConfig, targetType);
838
+ const parameters = window.neuralNetwork.calculateParameters(targetConfig, targetType);
839
+
840
+ if (outputShape) {
841
+ targetConfig.outputShape = outputShape;
842
+
843
+ // Update output shape display
844
+ const outputShapeDisplay = targetNode.querySelector('.output-shape');
845
+ if (outputShapeDisplay) {
846
+ outputShapeDisplay.textContent = `[${outputShape.join(' × ')}]`;
847
+ }
848
+ }
849
+
850
+ if (parameters !== undefined) {
851
+ targetConfig.parameters = parameters;
852
+
853
+ // Update parameters display
854
+ const paramsDisplay = targetNode.querySelector('.node-parameters');
855
+ if (paramsDisplay) {
856
+ paramsDisplay.textContent = `Params: ${formatNumber(parameters)}`;
857
+ }
858
+ }
859
+ } else {
860
+ // Fallback calculations if neural network module is not available
861
+ let outputShape, parameters;
862
+
863
+ switch (targetType) {
864
+ case 'hidden':
865
+ outputShape = [targetConfig.units || 64];
866
+ if (sourceConfig.outputShape) {
867
+ const inputSize = sourceConfig.outputShape.reduce((a, b) => a * b, 1);
868
+ parameters = inputSize * targetConfig.units + targetConfig.units; // weights + biases
869
+ }
870
+ break;
871
+
872
+ case 'output':
873
+ outputShape = [targetConfig.units || 10];
874
+ if (sourceConfig.outputShape) {
875
+ const inputSize = sourceConfig.outputShape.reduce((a, b) => a * b, 1);
876
+ parameters = inputSize * targetConfig.units + targetConfig.units; // weights + biases
877
+ }
878
+ break;
879
+
880
+ case 'conv':
881
+ if (sourceConfig.outputShape && sourceConfig.outputShape.length >= 3) {
882
+ // Very explicit type conversion - ensure all values are numbers
883
+ const height = Math.max(1, parseInt(sourceConfig.outputShape[0]) || 1); // Ensure at least 1
884
+ const width = Math.max(1, parseInt(sourceConfig.outputShape[1]) || 1); // Ensure at least 1
885
+ const channels = Math.max(1, parseInt(sourceConfig.outputShape[2]) || 1); // Ensure at least 1
886
+
887
+ console.log(`Conv2D CONNECTION INPUT SHAPE: [${height}, ${width}, ${channels}]`,
888
+ {original: sourceConfig.outputShape, parsed: [height, width, channels]});
889
+
890
+ // Ensure filters is a positive number
891
+ const filters = Math.max(1, parseInt(targetConfig.filters) || 32);
892
+
893
+ // Explicit processing of kernelSize with safety checks
894
+ let kernelSize = [3, 3]; // Default fallback
895
+ if (targetConfig.kernelSize) {
896
+ if (typeof targetConfig.kernelSize === 'string') {
897
+ kernelSize = targetConfig.kernelSize.split(',')
898
+ .map(v => Math.max(1, parseInt(v.trim()) || 1)); // Ensure at least 1
899
+ } else if (Array.isArray(targetConfig.kernelSize)) {
900
+ kernelSize = targetConfig.kernelSize
901
+ .map(v => Math.max(1, parseInt(v) || 1)); // Ensure at least 1
902
+ }
903
+ }
904
+
905
+ // Explicit processing of strides with safety checks
906
+ let strides = [1, 1]; // Default fallback
907
+ if (targetConfig.strides) {
908
+ if (typeof targetConfig.strides === 'string') {
909
+ strides = targetConfig.strides.split(',')
910
+ .map(v => Math.max(1, parseInt(v.trim()) || 1)); // Ensure at least 1
911
+ } else if (Array.isArray(targetConfig.strides)) {
912
+ strides = targetConfig.strides
913
+ .map(v => Math.max(1, parseInt(v) || 1)); // Ensure at least 1
914
+ }
915
+ }
916
+
917
+ // Ensure we have at least 2 elements for kernelSize and strides and all values are at least 1
918
+ kernelSize = kernelSize.length >= 2 ?
919
+ [Math.max(1, kernelSize[0]), Math.max(1, kernelSize[1])] :
920
+ [Math.max(1, kernelSize[0] || 3), Math.max(1, kernelSize[0] || 3)];
921
+
922
+ strides = strides.length >= 2 ?
923
+ [Math.max(1, strides[0]), Math.max(1, strides[1])] :
924
+ [Math.max(1, strides[0] || 1), Math.max(1, strides[0] || 1)];
925
+
926
+ console.log(`Conv2D CONNECTION CONFIG:`, {
927
+ filters: filters,
928
+ kernelSize: kernelSize,
929
+ strides: strides
930
+ });
931
+
932
+ // Store cleaned values back in config
933
+ targetConfig.filters = filters;
934
+ targetConfig.kernelSize = kernelSize;
935
+ targetConfig.strides = strides;
936
+
937
+ const padding = targetConfig.padding || 'same';
938
+
939
+ // Calculate output dimensions based on padding
940
+ let outHeight, outWidth;
941
+ if (padding === 'same') {
942
+ outHeight = Math.ceil(height / strides[0]);
943
+ outWidth = Math.ceil(width / strides[1]);
944
+ } else { // 'valid' padding
945
+ outHeight = Math.ceil((height - kernelSize[0] + 1) / strides[0]);
946
+ outWidth = Math.ceil((width - kernelSize[1] + 1) / strides[1]);
947
+ }
948
+
949
+ // Ensure output dimensions are at least 1
950
+ outHeight = Math.max(1, outHeight);
951
+ outWidth = Math.max(1, outWidth);
952
+
953
+ // Final output shape with proper validation
954
+ outputShape = [outHeight, outWidth, filters];
955
+
956
+ // Calculate parameters step by step to avoid any overflow or multiplication errors
957
+ const kh = Number(kernelSize[0]);
958
+ const kw = Number(kernelSize[1]);
959
+ const c = Number(channels);
960
+ const f = Number(filters);
961
+
962
+ // Check for any zeros or negative values that would make the calculation invalid
963
+ if (kh <= 0 || kw <= 0 || c <= 0 || f <= 0) {
964
+ console.error(`Invalid Conv2D connection parameter values: kh=${kh}, kw=${kw}, c=${c}, f=${f}`);
965
+ parameters = 0;
966
+ } else {
967
+ // Calculate with explicit steps to avoid any overflow
968
+ const kernelParams = kh * kw * c * f;
969
+ const biasParams = f;
970
+ parameters = kernelParams + biasParams;
971
+
972
+ console.log(`Conv2D CONNECTION CALCULATION STEPS:
973
+ Kernel height (kh) = ${kh}
974
+ Kernel width (kw) = ${kw}
975
+ Input channels (c) = ${c}
976
+ Filters (f) = ${f}
977
+ Kernel params = ${kh} × ${kw} × ${c} × ${f} = ${kernelParams}
978
+ Bias params = ${biasParams}
979
+ Total params = ${kernelParams} + ${biasParams} = ${parameters}
980
+ `);
981
+ }
982
+
983
+ console.log(`Conv2D connection output shape: ${outHeight}×${outWidth}×${filters}`);
984
+ } else {
985
+ console.log('Cannot calculate Conv2D connection parameters - invalid input shape:', sourceConfig.outputShape);
986
+ const filters = parseInt(targetConfig.filters) || 32;
987
+ outputShape = ['?', '?', filters];
988
+ parameters = 0; // Set to 0 instead of '?' to avoid display issues
989
+ }
990
+ break;
991
+
992
+ case 'pool':
993
+ if (sourceConfig.outputShape && sourceConfig.outputShape.length >= 3) {
994
+ const [height, width, channels] = sourceConfig.outputShape;
995
+ const poolSize = targetConfig.poolSize || [2, 2];
996
+ const stride = targetConfig.strides || poolSize;
997
+ const padding = targetConfig.padding || 'valid';
998
+
999
+ // Calculate output dimensions
1000
+ let outHeight, outWidth;
1001
+ if (padding === 'same') {
1002
+ outHeight = Math.ceil(height / stride[0]);
1003
+ outWidth = Math.ceil(width / stride[1]);
1004
+ } else { // 'valid' padding
1005
+ outHeight = Math.ceil((height - poolSize[0] + 1) / stride[0]);
1006
+ outWidth = Math.ceil((width - poolSize[1] + 1) / stride[1]);
1007
+ }
1008
+
1009
+ outputShape = [outHeight, outWidth, channels];
1010
+ parameters = 0; // Pooling layers have no parameters
1011
+ }
1012
+ break;
1013
+ }
1014
+
1015
+ // Update target config and display only for automatically calculated shapes
1016
+ if (outputShape) {
1017
+ targetConfig.outputShape = outputShape;
1018
+
1019
+ // Update output shape display
1020
+ const outputShapeDisplay = targetNode.querySelector('.output-shape');
1021
+ if (outputShapeDisplay) {
1022
+ outputShapeDisplay.textContent = `[${outputShape.join(' × ')}]`;
1023
+ }
1024
+ }
1025
+
1026
+ if (parameters !== undefined) {
1027
+ targetConfig.parameters = parameters;
1028
+
1029
+ // Update parameters display
1030
+ const paramsDisplay = targetNode.querySelector('.node-parameters');
1031
+ if (paramsDisplay) {
1032
+ paramsDisplay.textContent = `Params: ${formatNumber(parameters)}`;
1033
+ }
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ // Store updated config back to the node
1039
+ targetNode.layerConfig = targetConfig;
1040
+
1041
+ // Update model
1042
+ const layerIndex = networkLayers.layers.findIndex(layer => layer.id === targetId);
1043
+ if (layerIndex !== -1) {
1044
+ networkLayers.layers[layerIndex].config = targetConfig;
1045
+ if (targetConfig.parameters) {
1046
+ networkLayers.layers[layerIndex].parameters = targetConfig.parameters;
1047
+ }
1048
+ }
1049
+
1050
+ // Force re-render the node to show updated info
1051
+ const dimensions = targetNode.querySelector('.node-dimensions');
1052
+ if (dimensions && targetConfig.outputShape) {
1053
+ let dimensionsText = '';
1054
+ if (targetType === 'hidden' || targetType === 'output') {
1055
+ dimensionsText = targetConfig.units || '';
1056
+ } else if (targetType === 'conv' || targetType === 'pool') {
1057
+ dimensionsText = targetConfig.outputShape.join('×');
1058
+ }
1059
+ dimensions.textContent = dimensionsText;
1060
+ }
1061
+ }
1062
+
1063
+ // 4. EXPORT GLOBAL FUNCTIONS
1064
+
1065
+ // Expose functions to window for compatibility
1066
+ window.dragDrop = {
1067
+ getNetworkArchitecture: function() {
1068
+ return networkLayers;
1069
+ },
1070
+ clearAllNodes: function() {
1071
+ // Clear all nodes
1072
+ document.querySelectorAll('.canvas-node, .connection').forEach(el => {
1073
+ if (el.parentNode) {
1074
+ el.parentNode.removeChild(el);
1075
+ }
1076
+ });
1077
+
1078
+ // Reset model
1079
+ networkLayers = {
1080
+ layers: [],
1081
+ connections: []
1082
+ };
1083
+
1084
+ // Reset counters
1085
+ for (let key in nodeCounter) {
1086
+ nodeCounter[key] = 0;
1087
+ }
1088
+
1089
+ // Show hint
1090
+ const canvasHint = document.querySelector('.canvas-hint');
1091
+ if (canvasHint) {
1092
+ canvasHint.style.display = 'block';
1093
+ }
1094
+
1095
+ // Reset layer counter in neural network module
1096
+ if (window.neuralNetwork && window.neuralNetwork.resetLayerCounter) {
1097
+ window.neuralNetwork.resetLayerCounter();
1098
+ }
1099
+
1100
+ // Notify update
1101
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
1102
+ detail: networkLayers
1103
+ }));
1104
+ },
1105
+ updateConnections: updateConnections,
1106
+
1107
+ // Force update all node parameters in the network
1108
+ forceUpdateNetworkParameters: function() {
1109
+ console.log('Force updating all network parameters');
1110
+
1111
+ // Get all root nodes (nodes with no incoming connections)
1112
+ const rootNodes = [];
1113
+ const allNodeIds = networkLayers.layers.map(layer => layer.id);
1114
+ const targetNodeIds = networkLayers.connections.map(conn => conn.target);
1115
+
1116
+ allNodeIds.forEach(nodeId => {
1117
+ if (!targetNodeIds.includes(nodeId)) {
1118
+ rootNodes.push(nodeId);
1119
+ }
1120
+ });
1121
+
1122
+ console.log('Root nodes for parameter propagation:', rootNodes);
1123
+
1124
+ // Start update from root nodes
1125
+ rootNodes.forEach(nodeId => {
1126
+ updateDownstreamNodes(nodeId);
1127
+ });
1128
+
1129
+ // Recursive function to update downstream nodes
1130
+ function updateDownstreamNodes(nodeId) {
1131
+ console.log(`Updating downstream from node: ${nodeId}`);
1132
+
1133
+ // Find all connections from this node
1134
+ const outgoingConnections = networkLayers.connections.filter(conn => conn.source === nodeId);
1135
+
1136
+ // If no outgoing connections, we're done with this branch
1137
+ if (outgoingConnections.length === 0) {
1138
+ console.log(`Node ${nodeId} has no outgoing connections`);
1139
+ return;
1140
+ }
1141
+
1142
+ // Get source node and its config
1143
+ const sourceNode = document.querySelector(`.canvas-node[data-id="${nodeId}"]`);
1144
+ if (!sourceNode || !sourceNode.layerConfig) {
1145
+ console.warn(`Source node ${nodeId} not found or has no config`);
1146
+ return;
1147
+ }
1148
+
1149
+ const sourceConfig = sourceNode.layerConfig;
1150
+ const sourceType = sourceNode.getAttribute('data-type');
1151
+
1152
+ // Double check source outputShape is valid
1153
+ if (!sourceConfig.outputShape || !Array.isArray(sourceConfig.outputShape)) {
1154
+ console.warn(`Source node ${nodeId} (${sourceType}) has invalid output shape:`, sourceConfig.outputShape);
1155
+ // Try to fix based on node type
1156
+ if (sourceType === 'input' && Array.isArray(sourceConfig.shape)) {
1157
+ sourceConfig.outputShape = [...sourceConfig.shape];
1158
+ console.log(`Fixed input node output shape to:`, sourceConfig.outputShape);
1159
+ }
1160
+ }
1161
+
1162
+ console.log(`Source node ${nodeId} (${sourceType}) output shape:`, sourceConfig.outputShape);
1163
+
1164
+ // For each outgoing connection, update the target node
1165
+ outgoingConnections.forEach(conn => {
1166
+ const targetId = conn.target;
1167
+ const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`);
1168
+
1169
+ if (!targetNode) {
1170
+ console.warn(`Target node ${targetId} not found`);
1171
+ return;
1172
+ }
1173
+
1174
+ // Update target node
1175
+ const targetType = targetNode.getAttribute('data-type');
1176
+ const targetConfig = targetNode.layerConfig || {};
1177
+
1178
+ console.log(`Updating connection: ${sourceType}(${nodeId}) → ${targetType}(${targetId})`);
1179
+
1180
+ // Check if target has manually set output shape
1181
+ const hasManualOutputShape = targetConfig.outputShape &&
1182
+ Array.isArray(targetConfig.outputShape) &&
1183
+ targetConfig.outputShape.length > 0 &&
1184
+ targetConfig.outputShape.some(dim => dim !== '?' && dim !== '');
1185
+
1186
+ console.log(`Target node ${targetId} has manual output shape: ${hasManualOutputShape}`,
1187
+ targetConfig.outputShape);
1188
+
1189
+ // Set input shape of target based on output shape of source
1190
+ if (sourceConfig.outputShape) {
1191
+ // Make a deep copy to avoid reference issues
1192
+ targetConfig.inputShape = JSON.parse(JSON.stringify(sourceConfig.outputShape));
1193
+ console.log(`Set target node ${targetId} input shape to:`, targetConfig.inputShape);
1194
+
1195
+ // Update the input shape display
1196
+ const inputShapeDisplay = targetNode.querySelector('.input-shape');
1197
+ if (inputShapeDisplay) {
1198
+ inputShapeDisplay.textContent = `[${sourceConfig.outputShape.join(' × ')}]`;
1199
+ }
1200
+
1201
+ // Only update output shape if not manually set
1202
+ if (!hasManualOutputShape) {
1203
+ // Special handling for Conv2D
1204
+ if (targetType === 'conv') {
1205
+ console.log(`Special handling for Conv2D target node ${targetId}`);
1206
+
1207
+ // Force update the parameters
1208
+ if (window.updateParametersAfterConnection) {
1209
+ try {
1210
+ window.updateParametersAfterConnection(nodeId, targetId);
1211
+ console.log(`Updated Conv2D node ${targetId} parameters through connection handler`);
1212
+ } catch (error) {
1213
+ console.error(`Error updating Conv2D parameters:`, error);
1214
+ }
1215
+ } else {
1216
+ console.warn('updateParametersAfterConnection not available');
1217
+ }
1218
+ } else {
1219
+ // Use standard update for other node types
1220
+ if (window.updateParametersAfterConnection) {
1221
+ window.updateParametersAfterConnection(nodeId, targetId);
1222
+ } else {
1223
+ // Otherwise, manually update the target node
1224
+ updateNodeDisplay(targetNode, targetConfig);
1225
+ }
1226
+ }
1227
+ } else {
1228
+ console.log(`Preserving manual output shape for node ${targetId}:`, targetConfig.outputShape);
1229
+
1230
+ // Still update parameters even if output shape is manual
1231
+ if (window.neuralNetwork && window.neuralNetwork.calculateParameters) {
1232
+ try {
1233
+ const parameters = window.neuralNetwork.calculateParameters(targetConfig, targetType);
1234
+ if (parameters !== undefined) {
1235
+ targetConfig.parameters = parameters;
1236
+
1237
+ // Update parameters display
1238
+ const paramsDisplay = targetNode.querySelector('.node-parameters');
1239
+ if (paramsDisplay) {
1240
+ paramsDisplay.textContent = `Params: ${formatNumber(parameters)}`;
1241
+ }
1242
+ }
1243
+ } catch (error) {
1244
+ console.error(`Error calculating parameters with manual shape:`, error);
1245
+ }
1246
+ }
1247
+ }
1248
+
1249
+ // Store updated config back to the node
1250
+ targetNode.layerConfig = targetConfig;
1251
+
1252
+ // Continue propagation down the network
1253
+ updateDownstreamNodes(targetId);
1254
+ } else {
1255
+ console.warn(`Source node ${nodeId} has no output shape, cannot update target ${targetId}`);
1256
+ }
1257
+ });
1258
+ }
1259
+
1260
+ // Update node's display without trigger events that would cause loops
1261
+ function updateNodeDisplay(node, config) {
1262
+ if (!node) return;
1263
+
1264
+ const nodeType = node.getAttribute('data-type');
1265
+ node.layerConfig = config;
1266
+
1267
+ // Update input shape display
1268
+ const inputShapeDisplay = node.querySelector('.input-shape');
1269
+ if (inputShapeDisplay && config.inputShape) {
1270
+ inputShapeDisplay.textContent = `[${config.inputShape.join(' × ')}]`;
1271
+ }
1272
+
1273
+ // Other updates would depend on neural network module
1274
+ // This is just a basic update without recalculating everything
1275
+ }
1276
+
1277
+ // Update all connections visually
1278
+ updateConnections();
1279
+
1280
+ // Notify that network has been updated
1281
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
1282
+ detail: networkLayers
1283
+ }));
1284
+
1285
+ console.log('Finished force updating network parameters');
1286
+ }
1287
+ };
1288
+
1289
+ // Add global connection handlers for compatibility with existing code
1290
+ window.startConnection = startConnectionHandler;
1291
+ window.updateParametersAfterConnection = updateParametersAfterConnection;
1292
+
1293
+ // Debugging help
1294
+ console.log('Complete drag and drop fix initialized');
1295
+
1296
+ // Add a button to manually fix Conv2D parameters
1297
+ function addConv2DFixButton() {
1298
+ // Check if button already exists
1299
+ if (document.getElementById('fix-conv2d-button')) {
1300
+ return;
1301
+ }
1302
+
1303
+ // Create the button
1304
+ const fixButton = document.createElement('button');
1305
+ fixButton.id = 'fix-conv2d-button';
1306
+ fixButton.textContent = 'Fix Conv2D Params';
1307
+ fixButton.title = 'Manually recalculate parameters for Conv2D nodes';
1308
+
1309
+ // Style the button
1310
+ Object.assign(fixButton.style, {
1311
+ position: 'absolute',
1312
+ right: '10px',
1313
+ top: '10px',
1314
+ zIndex: '9999',
1315
+ padding: '5px 10px',
1316
+ backgroundColor: '#4285f4',
1317
+ color: 'white',
1318
+ border: 'none',
1319
+ borderRadius: '4px',
1320
+ cursor: 'pointer',
1321
+ fontSize: '12px',
1322
+ fontWeight: 'bold',
1323
+ boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
1324
+ });
1325
+
1326
+ // Add hover effect
1327
+ fixButton.onmouseover = function() {
1328
+ this.style.backgroundColor = '#3367d6';
1329
+ };
1330
+ fixButton.onmouseout = function() {
1331
+ this.style.backgroundColor = '#4285f4';
1332
+ };
1333
+
1334
+ // Add click handler
1335
+ fixButton.addEventListener('click', function() {
1336
+ console.log('Manually fixing Conv2D parameters...');
1337
+
1338
+ // Check if our helper function exists
1339
+ if (window.forceRecalculateConv2DParameters) {
1340
+ window.forceRecalculateConv2DParameters();
1341
+ fixButton.textContent = 'Conv2D Fixed!';
1342
+ setTimeout(() => {
1343
+ fixButton.textContent = 'Fix Conv2D Params';
1344
+ }, 2000);
1345
+ } else {
1346
+ console.error('Conv2D helper function not found');
1347
+ alert('Conv2D helper function not found! Please refresh the page and try again.');
1348
+ }
1349
+ });
1350
+
1351
+ // Add to body
1352
+ document.body.appendChild(fixButton);
1353
+ console.log('Added Conv2D fix button');
1354
+ }
1355
+ }
1356
+ })();
js/debug-utils.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Debug utilities for Neural Network Playground
2
+ (function() {
3
+ // Debug utilities are disabled to prevent notifications
4
+
5
+ // Function to display debug information (not called by default)
6
+ function showDebugInfo() {
7
+ // Debug functionality is disabled
8
+ }
9
+
10
+ // Create a temporary visual overlay showing node IDs (not called by default)
11
+ function createDebugOverlay() {
12
+ // Debug overlay is disabled
13
+ }
14
+ })();
js/drag-drop-cleanup.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Drag and drop cleanup script
2
+ (function() {
3
+ // Silent cleanup script - no console logs
4
+
5
+ // Monitor and clean up any accidental global drag variables
6
+ setInterval(function() {
7
+ // Check for accidental global draggedNode variable
8
+ if (window.draggedNode !== undefined) {
9
+ delete window.draggedNode;
10
+ }
11
+
12
+ // Check if a drag operation was left hanging (older browsers/edge cases)
13
+ if (document.querySelectorAll('.temp-connection').length > 0 &&
14
+ !document.querySelector('.node-port.active-port')) {
15
+ document.querySelectorAll('.temp-connection').forEach(el => {
16
+ if (el.parentNode) {
17
+ el.parentNode.removeChild(el);
18
+ }
19
+ });
20
+ }
21
+
22
+ // Remove any port highlighting if no active connection is in progress
23
+ const activePorts = document.querySelectorAll('.active-port, .valid-target, .invalid-target, .port-hover');
24
+ if (activePorts.length > 0 && document.querySelectorAll('.temp-connection').length === 0) {
25
+ activePorts.forEach(port => {
26
+ port.classList.remove('active-port', 'valid-target', 'invalid-target', 'port-hover');
27
+ });
28
+ }
29
+
30
+ // Clean up any stray dragging classes that might be stuck
31
+ if (!document.querySelector('.dragging')) {
32
+ document.body.classList.remove('node-dragging');
33
+ }
34
+ }, 5000); // Check every 5 seconds
35
+
36
+ // Add listener to clean up on page events
37
+ ['mouseup', 'dragend'].forEach(eventName => {
38
+ document.addEventListener(eventName, function() {
39
+ // Delay cleanup to allow normal handlers to run first
40
+ setTimeout(function() {
41
+ if (window.draggedNode !== undefined) {
42
+ delete window.draggedNode;
43
+ }
44
+ }, 100);
45
+ });
46
+ });
47
+
48
+ // Perform initial cleanup when the script loads
49
+ document.addEventListener('DOMContentLoaded', function() {
50
+ // Remove any dragging classes that might be present from a previous session
51
+ document.querySelectorAll('.dragging').forEach(node => {
52
+ node.classList.remove('dragging');
53
+ });
54
+ document.body.classList.remove('node-dragging');
55
+
56
+ // Reset any z-index values that might be stuck
57
+ document.querySelectorAll('.canvas-node').forEach(node => {
58
+ node.style.zIndex = '10';
59
+ });
60
+ });
61
+ })();
js/drag-drop-debug.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Drag and drop debugging script
2
+ (function() {
3
+ console.log('Drag and Drop Debug Tool Loaded');
4
+
5
+ document.addEventListener('DOMContentLoaded', function() {
6
+ console.log('DOM Content Loaded - Attaching Debug Handlers');
7
+ setTimeout(setupDebugHandlers, 1000); // Give time for other scripts to initialize
8
+ });
9
+
10
+ function setupDebugHandlers() {
11
+ console.log('Setting up drag-drop debug handlers');
12
+
13
+ // Debug canvas events
14
+ const canvas = document.getElementById('network-canvas');
15
+ if (!canvas) {
16
+ console.error('ERROR: Canvas element not found!');
17
+ return;
18
+ }
19
+
20
+ // Debug existing nodes
21
+ const existingNodes = document.querySelectorAll('.canvas-node');
22
+ console.log(`Found ${existingNodes.length} existing nodes on the canvas`);
23
+
24
+ existingNodes.forEach((node, index) => {
25
+ const nodeId = node.getAttribute('data-id') || `unknown-${index}`;
26
+ const nodeType = node.getAttribute('data-type') || 'unknown';
27
+ console.log(`Node #${index}: ${nodeType} (${nodeId})`);
28
+
29
+ // Add debug mousedown listener
30
+ node.addEventListener('mousedown', function(e) {
31
+ console.log(`[DEBUG] Mousedown on node: ${nodeId}`);
32
+ console.log(`Target element: ${e.target.className}`);
33
+ console.log(`Target has controls? ${!!e.target.closest('.node-controls')}`);
34
+ console.log(`Target has port? ${!!e.target.closest('.node-port')}`);
35
+ });
36
+ });
37
+
38
+ // Monitor mouse events over the canvas
39
+ canvas.addEventListener('mousemove', function(e) {
40
+ // Only log occasionally to avoid flooding console
41
+ if (Math.random() < 0.01) { // Log approximately 1% of moves
42
+ const rect = canvas.getBoundingClientRect();
43
+ const x = e.clientX - rect.left;
44
+ const y = e.clientY - rect.top;
45
+ console.log(`[DEBUG] Mouse at (${Math.round(x)}, ${Math.round(y)})`);
46
+
47
+ // Check for drag states
48
+ const dragInProgress = document.querySelector('.canvas-node.dragging');
49
+ if (dragInProgress) {
50
+ console.log(`[DEBUG] Dragging node: ${dragInProgress.getAttribute('data-id')}`);
51
+ }
52
+ }
53
+ });
54
+
55
+ // Monitor startDrag and dragNode functions if they exist
56
+ if (window.startDrag) {
57
+ const originalStartDrag = window.startDrag;
58
+ window.startDrag = function(e) {
59
+ console.log('[DEBUG] startDrag called', e.target);
60
+ return originalStartDrag.apply(this, arguments);
61
+ };
62
+ }
63
+
64
+ if (window.dragNode) {
65
+ const originalDragNode = window.dragNode;
66
+ window.dragNode = function(e) {
67
+ // Log only occasionally
68
+ if (Math.random() < 0.05) {
69
+ console.log('[DEBUG] dragNode called');
70
+ }
71
+ return originalDragNode.apply(this, arguments);
72
+ };
73
+ }
74
+
75
+ // Check for global variables that might be interfering
76
+ setInterval(function() {
77
+ console.log('Checking global drag variables:');
78
+ console.log('window.draggedNode:', window.draggedNode !== undefined);
79
+ console.log('window.isDragging:', window.isDragging !== undefined);
80
+
81
+ // Count nodes with dragging class
82
+ const draggingNodes = document.querySelectorAll('.canvas-node.dragging');
83
+ if (draggingNodes.length > 0) {
84
+ console.log(`[WARNING] Found ${draggingNodes.length} nodes with dragging class, but no active drag`);
85
+ }
86
+ }, 5000);
87
+
88
+ console.log('Debug handlers setup complete');
89
+ }
90
+ })();
js/drag-drop.js CHANGED
@@ -10,6 +10,15 @@ function initializeDragAndDrop() {
10
  let connectionLine = null;
11
  let nodeCounter = {};
12
 
 
 
 
 
 
 
 
 
 
13
  // Track layers for proper architecture building
14
  let networkLayers = {
15
  layers: [],
@@ -27,250 +36,436 @@ function initializeDragAndDrop() {
27
  return num.toString();
28
  }
29
 
30
- // Add event listeners to draggable items
31
  nodeItems.forEach(item => {
32
- item.addEventListener('dragstart', handleDragStart);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  });
34
 
35
- // Canvas events for dropping nodes
36
- canvas.addEventListener('dragover', handleDragOver);
37
- canvas.addEventListener('drop', handleDrop);
38
-
39
- // Handle drag start event
40
- function handleDragStart(e) {
41
- draggedNode = this;
42
- e.dataTransfer.setData('text/plain', this.getAttribute('data-type'));
43
-
44
- // Set a ghost image for drag (optional)
45
- const ghost = this.cloneNode(true);
46
- ghost.style.opacity = '0.5';
47
- document.body.appendChild(ghost);
48
- e.dataTransfer.setDragImage(ghost, 0, 0);
49
- setTimeout(() => {
50
- document.body.removeChild(ghost);
51
- }, 0);
52
- }
53
-
54
- // Handle drag over event
55
  function handleDragOver(e) {
56
  e.preventDefault();
57
  e.dataTransfer.dropEffect = 'copy';
58
  }
59
 
60
- // Handle drop event to create new nodes on the canvas
61
- function handleDrop(e) {
 
 
 
62
  e.preventDefault();
63
 
64
- // Hide the canvas hint when nodes are added
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  const canvasHint = document.querySelector('.canvas-hint');
66
  if (canvasHint) {
67
  canvasHint.style.display = 'none';
68
  }
69
 
70
- const nodeType = e.dataTransfer.getData('text/plain');
 
 
71
 
72
- if (nodeType) {
73
- // Generate unique layer ID
74
- const layerId = window.neuralNetwork.getNextLayerId(nodeType);
75
-
76
- // Create a new node on the canvas
77
- const canvasNode = document.createElement('div');
78
- canvasNode.className = `canvas-node ${nodeType}-node`;
79
- canvasNode.setAttribute('data-type', nodeType);
80
- canvasNode.setAttribute('data-id', layerId);
81
-
82
- // Set node position
83
- const rect = canvas.getBoundingClientRect();
84
- const x = e.clientX - rect.left;
85
- const y = e.clientY - rect.top;
86
-
87
- canvasNode.style.left = `${x}px`;
88
- canvasNode.style.top = `${y}px`;
89
-
90
- // Get default config for this node type
91
- const nodeConfig = window.neuralNetwork.createNodeConfig(nodeType);
92
-
93
- // Create node content with input and output shape information
94
- let nodeName, inputShape, outputShape, parameters;
95
-
96
- switch(nodeType) {
97
- case 'input':
98
- nodeName = 'Input Layer';
99
- inputShape = 'N/A';
100
- outputShape = '[' + nodeConfig.shape.join(' × ') + ']';
101
- parameters = nodeConfig.parameters;
102
- break;
103
- case 'hidden':
104
- const hiddenCount = document.querySelectorAll('.canvas-node[data-type="hidden"]').length;
105
- nodeConfig.units = hiddenCount === 0 ? 128 : 64;
106
- nodeName = `Hidden Layer ${hiddenCount + 1}`;
107
- // Input shape will be updated when connections are made
108
- inputShape = 'Connect input';
109
- outputShape = `[${nodeConfig.units}]`;
110
- parameters = 'Connect input to calculate';
111
- break;
112
- case 'output':
113
- nodeName = 'Output Layer';
114
- inputShape = 'Connect input';
115
- outputShape = `[${nodeConfig.units}]`;
116
- parameters = 'Connect input to calculate';
117
- break;
118
- case 'conv':
119
- const convCount = document.querySelectorAll('.canvas-node[data-type="conv"]').length;
120
- nodeConfig.filters = 32 * (convCount + 1);
121
- nodeName = `Conv2D ${convCount + 1}`;
122
- inputShape = 'Connect input';
123
- outputShape = 'Depends on input';
124
- // Create parameter string
125
- parameters = `Kernel: ${nodeConfig.kernelSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`;
126
- break;
127
- case 'pool':
128
- const poolCount = document.querySelectorAll('.canvas-node[data-type="pool"]').length;
129
- nodeName = `Pooling ${poolCount + 1}`;
130
- inputShape = 'Connect input';
131
- outputShape = 'Depends on input';
132
- parameters = `Pool size: ${nodeConfig.poolSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`;
133
- break;
134
- default:
135
- nodeName = 'Unknown Layer';
136
- inputShape = 'N/A';
137
- outputShape = 'N/A';
138
- parameters = 'N/A';
139
  }
140
-
141
- // Create node content
142
- const nodeContent = document.createElement('div');
143
- nodeContent.className = 'node-content';
144
-
145
- // Add shape information in a structured way
146
- const shapeInfo = document.createElement('div');
147
- shapeInfo.className = 'shape-info';
148
- shapeInfo.innerHTML = `
149
- <div class="shape-row"><span class="shape-label">Input:</span> <span class="input-shape">${inputShape}</span></div>
150
- <div class="shape-row"><span class="shape-label">Output:</span> <span class="output-shape">${outputShape}</span></div>
151
- `;
152
-
153
- // Add parameters section
154
- const paramsSection = document.createElement('div');
155
- paramsSection.className = 'params-section';
156
- paramsSection.innerHTML = `
157
- <div class="params-details">${parameters}</div>
158
- <div class="node-parameters">Params: ${nodeConfig.parameters !== undefined ? formatNumber(nodeConfig.parameters) : '?'}</div>
159
- `;
160
-
161
- // Assemble content
162
- nodeContent.appendChild(shapeInfo);
163
- nodeContent.appendChild(paramsSection);
164
-
165
- // Add dimensions section to show shapes compactly
166
- const dimensionsSection = document.createElement('div');
167
- dimensionsSection.className = 'node-dimensions';
168
-
169
- // Set dimensions text based on node type
170
- let dimensionsText = '';
171
- switch(nodeType) {
172
- case 'input':
173
- dimensionsText = nodeConfig.shape.join(' × ');
174
- break;
175
- case 'hidden':
176
- case 'output':
177
- dimensionsText = nodeConfig.units.toString();
178
- break;
179
- case 'conv':
180
- if (nodeConfig.inputShape && nodeConfig.outputShape) {
181
- dimensionsText = `${nodeConfig.inputShape.join('×')} → ${nodeConfig.outputShape.join('×')}`;
182
- } else {
183
- dimensionsText = `? → ${nodeConfig.filters} filters`;
184
- }
185
- break;
186
- case 'pool':
187
- if (nodeConfig.inputShape && nodeConfig.outputShape) {
188
- dimensionsText = `${nodeConfig.inputShape.join('×')} → ${nodeConfig.outputShape.join('×')}`;
189
- } else {
190
- dimensionsText = `? → ?`;
191
- }
192
- break;
193
- case 'linear':
194
- dimensionsText = `${nodeConfig.inputFeatures} → ${nodeConfig.outputFeatures}`;
195
- break;
196
  }
197
- dimensionsSection.textContent = dimensionsText;
198
-
199
- // Add node title for clearer identification
200
- const nodeTitle = document.createElement('div');
201
- nodeTitle.className = 'node-title';
202
- nodeTitle.textContent = nodeName;
203
-
204
- // Add connection ports
205
- const portIn = document.createElement('div');
206
- portIn.className = 'node-port port-in';
207
-
208
- const portOut = document.createElement('div');
209
- portOut.className = 'node-port port-out';
210
-
211
- // Assemble the node with the new structure
212
- canvasNode.appendChild(nodeTitle);
213
- canvasNode.appendChild(dimensionsSection);
214
- canvasNode.appendChild(nodeContent);
215
- canvasNode.appendChild(portIn);
216
- canvasNode.appendChild(portOut);
217
-
218
- // Store node data attributes for easier access
219
- canvasNode.setAttribute('data-name', nodeName);
220
- canvasNode.setAttribute('data-dimensions', dimensionsText);
221
-
222
- // Add node to the canvas
223
- canvas.appendChild(canvasNode);
224
-
225
- // Store node configuration
226
- canvasNode.layerConfig = nodeConfig;
227
-
228
- // Add event listeners for node manipulation
229
- canvasNode.addEventListener('mousedown', startDrag);
230
-
231
- // Update port event listeners for the new class names
232
- portIn.addEventListener('mousedown', (e) => {
233
- e.stopPropagation();
234
- });
235
-
236
- portOut.addEventListener('mousedown', (e) => {
237
- e.stopPropagation();
238
- startConnection(canvasNode, e);
239
- });
240
-
241
- // Double-click to edit node properties
242
- canvasNode.addEventListener('dblclick', () => {
243
- openLayerEditor(canvasNode);
244
- });
245
-
246
- // Right-click to delete
247
- canvasNode.addEventListener('contextmenu', (e) => {
248
- e.preventDefault();
249
- deleteNode(canvasNode);
250
- });
251
-
252
- // Add to network layers for architecture building
253
- networkLayers.layers.push({
254
- id: layerId,
255
- type: nodeType,
256
- name: nodeName,
257
- position: { x, y },
258
- dimensions: dimensionsText,
259
- config: nodeConfig,
260
- parameters: nodeConfig.parameters || 0
261
- });
262
-
263
- // Notify about network changes
264
- document.dispatchEvent(new CustomEvent('networkUpdated', {
265
- detail: networkLayers
266
- }));
267
-
268
- updateConnections();
269
  }
 
 
 
 
 
 
 
 
270
  }
271
 
272
  // Start dragging an existing node on the canvas
273
  function startDrag(e) {
 
 
274
  if (isConnecting) return;
275
 
276
  // Only start drag if not clicking on buttons or ports
@@ -279,13 +474,20 @@ function initializeDragAndDrop() {
279
  }
280
 
281
  isDragging = true;
 
282
  const target = e.target.closest('.canvas-node');
 
 
 
 
 
283
  const rect = target.getBoundingClientRect();
284
 
285
  // Calculate offset
286
  offsetX = e.clientX - rect.left;
287
  offsetY = e.clientY - rect.top;
288
 
 
289
  document.addEventListener('mousemove', dragNode);
290
  document.addEventListener('mouseup', stopDrag);
291
 
@@ -298,13 +500,21 @@ function initializeDragAndDrop() {
298
  // Add dragging class for visual feedback
299
  draggedNode.classList.add('dragging');
300
 
 
 
 
301
  // Prevent default behavior
302
  e.preventDefault();
 
 
303
  }
304
 
305
  // Drag node on the canvas
306
  function dragNode(e) {
307
- if (!isDragging) return;
 
 
 
308
 
309
  const canvasRect = canvas.getBoundingClientRect();
310
  let x = e.clientX - canvasRect.left - offsetX;
@@ -331,25 +541,39 @@ function initializeDragAndDrop() {
331
  networkLayers.layers[layerIndex].position = { x, y };
332
  }
333
 
334
- // Update connected lines if any
335
  updateConnections();
336
  }
337
 
338
  // Stop dragging
339
- function stopDrag() {
340
- if (!isDragging) return;
 
 
341
 
342
- isDragging = false;
 
 
343
  document.removeEventListener('mousemove', dragNode);
344
  document.removeEventListener('mouseup', stopDrag);
345
 
346
- // Reset z-index and remove dragging class
 
 
 
 
 
347
  if (draggedNode) {
348
  draggedNode.style.zIndex = "10";
349
  draggedNode.classList.remove('dragging');
350
 
351
  // Trigger connections update one more time
352
  updateConnections();
 
 
 
 
 
353
  }
354
  }
355
 
@@ -363,7 +587,7 @@ function initializeDragAndDrop() {
363
  connectionLine.className = 'connection temp-connection';
364
 
365
  // Get start position (center of the port)
366
- const portOut = node.querySelector('.port-out');
367
  const portRect = portOut.getBoundingClientRect();
368
  const canvasRect = canvas.getBoundingClientRect();
369
 
@@ -402,11 +626,13 @@ function initializeDragAndDrop() {
402
  const nodeId = node.getAttribute('data-id');
403
  const isValidTarget = isValidConnection(sourceType, nodeType, sourceId, nodeId);
404
 
405
- const portIn = node.querySelector('.port-in');
406
- if (isValidTarget) {
407
- portIn.classList.add('valid-target');
408
- } else {
409
- portIn.classList.add('invalid-target');
 
 
410
  }
411
  }
412
  });
@@ -414,7 +640,7 @@ function initializeDragAndDrop() {
414
 
415
  // Remove highlights from all ports
416
  function removePortHighlights() {
417
- document.querySelectorAll('.port-in, .port-out').forEach(port => {
418
  port.classList.remove('active-port', 'valid-target', 'invalid-target');
419
  });
420
  }
@@ -454,7 +680,7 @@ function initializeDragAndDrop() {
454
  if (!isConnecting || !connectionLine) return;
455
 
456
  const canvasRect = canvas.getBoundingClientRect();
457
- const portOut = startNode.querySelector('.port-out');
458
  const portRect = portOut.getBoundingClientRect();
459
 
460
  // Calculate start and end points
@@ -474,16 +700,17 @@ function initializeDragAndDrop() {
474
  // Highlight the port under cursor
475
  document.querySelectorAll('.canvas-node').forEach(node => {
476
  if (node !== startNode) {
477
- const nodeRect = node.getBoundingClientRect();
478
- const portIn = node.querySelector('.port-in');
479
- const portInRect = portIn.getBoundingClientRect();
480
-
481
- // Check if mouse is over the input port
482
- if (e.clientX >= portInRect.left && e.clientX <= portInRect.right &&
483
- e.clientY >= portInRect.top && e.clientY <= portInRect.bottom) {
484
- portIn.classList.add('port-hover');
485
- } else {
486
- portIn.classList.remove('port-hover');
 
487
  }
488
  }
489
  });
@@ -497,20 +724,22 @@ function initializeDragAndDrop() {
497
  let targetNode = null;
498
  document.querySelectorAll('.canvas-node').forEach(node => {
499
  if (node !== startNode) {
500
- const portIn = node.querySelector('.port-in');
501
- const portRect = portIn.getBoundingClientRect();
502
-
503
- if (e.clientX >= portRect.left && e.clientX <= portRect.right &&
504
- e.clientY >= portRect.top && e.clientY <= portRect.bottom) {
505
-
506
- // Check if this would be a valid connection
507
- const sourceType = startNode.getAttribute('data-type');
508
- const targetType = node.getAttribute('data-type');
509
- const sourceId = startNode.getAttribute('data-id');
510
- const targetId = node.getAttribute('data-id');
511
 
512
- if (isValidConnection(sourceType, targetType, sourceId, targetId)) {
513
- targetNode = node;
 
 
 
 
 
 
 
 
 
 
514
  }
515
  }
516
  }
@@ -528,7 +757,7 @@ function initializeDragAndDrop() {
528
 
529
  // Remove all port highlights
530
  removePortHighlights();
531
- document.querySelectorAll('.port-hover').forEach(port => {
532
  port.classList.remove('port-hover');
533
  });
534
 
@@ -553,26 +782,499 @@ function initializeDragAndDrop() {
553
 
554
  // Check if this is a valid connection
555
  if (isValidConnection(sourceType, targetType, sourceId, targetId)) {
556
- // Create a permanent SVG connection
557
- const canvas = document.getElementById('network-canvas');
558
- const svgContainer = document.querySelector('#network-canvas .svg-container') || createSVGContainer();
 
559
 
560
- // Get positions for source and target nodes
561
- const sourceRect = startNode.getBoundingClientRect();
562
- const targetRect = targetNode.getBoundingClientRect();
563
- const canvasRect = canvas.getBoundingClientRect();
 
564
 
565
- // Calculate port positions
566
- const sourcePort = startNode.querySelector('.output-port');
567
- const targetPort = targetNode.querySelector('.input-port');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
 
569
- const sourcePortRect = sourcePort.getBoundingClientRect();
570
- const targetPortRect = targetPort.getBoundingClientRect();
571
 
572
- const startX = sourcePortRect.left + (sourcePortRect.width / 2) - canvasRect.left;
573
- const startY = sourcePortRect.top + (sourcePortRect.height / 2) - canvasRect.top;
574
- const endX = targetPortRect.left + (targetPortRect.width / 2) - canvasRect.left;
575
- const endY = targetPortRect.top + (targetPortRect.height / 2) - canvasRect.top;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
 
577
- // Create the connection
578
- const pathId = `connection-${sourceId}-${targetId}`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  let connectionLine = null;
11
  let nodeCounter = {};
12
 
13
+ // Anti-duplication system
14
+ const recentlyCreated = {
15
+ nodeIds: new Set(),
16
+ dragStartTime: 0,
17
+ isDropHandled: false,
18
+ inProgress: false,
19
+ timestamp: 0
20
+ };
21
+
22
  // Track layers for proper architecture building
23
  let networkLayers = {
24
  layers: [],
 
36
  return num.toString();
37
  }
38
 
39
+ // Add event listeners to draggable items with extra safety
40
  nodeItems.forEach(item => {
41
+ // Clean dragstart handler with precise lifecycle
42
+ item.addEventListener('dragstart', function(e) {
43
+ // Clean up previous state
44
+ recentlyCreated.isDropHandled = false;
45
+ recentlyCreated.inProgress = true;
46
+ recentlyCreated.dragStartTime = Date.now();
47
+
48
+ const nodeType = this.getAttribute('data-type');
49
+
50
+ // Persist data in multiple ways to ensure transfer
51
+ e.dataTransfer.setData('text/plain', nodeType);
52
+ e.dataTransfer.setData('application/x-neural-node-type', nodeType);
53
+
54
+ // Extra backup properties
55
+ try {
56
+ e.dataTransfer.nodeType = nodeType;
57
+ e.dataTransfer._neural_type = nodeType;
58
+ } catch (err) {
59
+ // Some browsers restrict properties on dataTransfer
60
+ }
61
+
62
+ draggedNode = this;
63
+
64
+ // Set ghost image
65
+ const ghost = this.cloneNode(true);
66
+ ghost.style.opacity = '0.5';
67
+ document.body.appendChild(ghost);
68
+ e.dataTransfer.setDragImage(ghost, 0, 0);
69
+ setTimeout(() => {
70
+ document.body.removeChild(ghost);
71
+ }, 0);
72
+
73
+ // Event cleanup handler
74
+ const dragEndHandler = function() {
75
+ setTimeout(() => {
76
+ recentlyCreated.inProgress = false;
77
+ draggedNode = null;
78
+ }, 100);
79
+ // Remove this one-time handler
80
+ item.removeEventListener('dragend', dragEndHandler);
81
+ };
82
+
83
+ // Add one-time dragend handler
84
+ item.addEventListener('dragend', dragEndHandler);
85
+ });
86
  });
87
 
88
+ // Safe dragover handler
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  function handleDragOver(e) {
90
  e.preventDefault();
91
  e.dataTransfer.dropEffect = 'copy';
92
  }
93
 
94
+ // Canvas events
95
+ canvas.addEventListener('dragover', handleDragOver);
96
+
97
+ // One-time drop handler for each operation
98
+ canvas.addEventListener('drop', function dropHandler(e) {
99
  e.preventDefault();
100
 
101
+ // Multi-layer protection against duplicate drops
102
+ if (recentlyCreated.isDropHandled) {
103
+ return; // Already handled this drop
104
+ }
105
+
106
+ const now = Date.now();
107
+
108
+ // Debounce protection
109
+ if (now - recentlyCreated.timestamp < 500) {
110
+ return;
111
+ }
112
+
113
+ // Set state to prevent multiple processing
114
+ recentlyCreated.isDropHandled = true;
115
+ recentlyCreated.timestamp = now;
116
+
117
+ // Safety check for drag operation
118
+ if (!recentlyCreated.inProgress || !draggedNode || !draggedNode.classList.contains('node-item')) {
119
+ return;
120
+ }
121
+
122
+ // Try multiple ways to get the node type
123
+ let nodeType = null;
124
+ try {
125
+ // Try standard method first
126
+ nodeType = e.dataTransfer.getData('text/plain');
127
+
128
+ // Try backup methods if needed
129
+ if (!nodeType) {
130
+ nodeType = e.dataTransfer.getData('application/x-neural-node-type');
131
+ }
132
+ if (!nodeType && e.dataTransfer.nodeType) {
133
+ nodeType = e.dataTransfer.nodeType;
134
+ }
135
+ if (!nodeType && e.dataTransfer._neural_type) {
136
+ nodeType = e.dataTransfer._neural_type;
137
+ }
138
+ if (!nodeType && draggedNode) {
139
+ nodeType = draggedNode.getAttribute('data-type');
140
+ }
141
+ } catch (err) {
142
+ // Error handling for dataTransfer access
143
+ }
144
+
145
+ if (!nodeType) {
146
+ return;
147
+ }
148
+
149
+ // Calculate position relative to canvas
150
+ const canvasRect = canvas.getBoundingClientRect();
151
+ const x = e.clientX - canvasRect.left - 75;
152
+ const y = e.clientY - canvasRect.top - 30;
153
+
154
+ // Ensure position is within canvas bounds
155
+ const posX = Math.max(0, Math.min(canvasRect.width - 150, x));
156
+ const posY = Math.max(0, Math.min(canvasRect.height - 100, y));
157
+
158
+ // Generate a unique ID for the node that includes a timestamp to avoid collision
159
+ const layerId = `${nodeType}-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
160
+
161
+ // Skip if this node ID was recently created (extremely unlikely due to timestamp)
162
+ if (recentlyCreated.nodeIds.has(layerId)) {
163
+ return;
164
+ }
165
+ recentlyCreated.nodeIds.add(layerId);
166
+
167
+ // Limit the size of the recently created set
168
+ if (recentlyCreated.nodeIds.size > 10) {
169
+ const iterator = recentlyCreated.nodeIds.values();
170
+ recentlyCreated.nodeIds.delete(iterator.next().value);
171
+ }
172
+
173
+ // Increment counter for this node type
174
+ nodeCounter[nodeType] = (nodeCounter[nodeType] || 0) + 1;
175
+
176
+ // Generate a unique ID for the node
177
+ const layerId = `${nodeType}-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
178
+
179
+ // Create the node element
180
+ const canvasNode = document.createElement('div');
181
+ canvasNode.className = `canvas-node ${nodeType}-node`;
182
+ canvasNode.setAttribute('data-type', nodeType);
183
+ canvasNode.setAttribute('data-id', layerId);
184
+ canvasNode.style.position = 'absolute';
185
+ canvasNode.style.left = `${posX}px`;
186
+ canvasNode.style.top = `${posY}px`;
187
+
188
+ // Get default config for this node type
189
+ const nodeConfig = window.neuralNetwork.createNodeConfig(nodeType);
190
+
191
+ // Create node content with input and output shape information
192
+ let nodeName, inputShape, outputShape, parameters;
193
+
194
+ switch(nodeType) {
195
+ case 'input':
196
+ nodeName = 'Input Layer';
197
+ inputShape = 'N/A';
198
+ outputShape = '[' + nodeConfig.shape.join(' × ') + ']';
199
+ parameters = nodeConfig.parameters;
200
+ break;
201
+ case 'hidden':
202
+ const hiddenCount = document.querySelectorAll('.canvas-node[data-type="hidden"]').length;
203
+ nodeConfig.units = hiddenCount === 0 ? 128 : 64;
204
+ nodeName = `Hidden Layer ${hiddenCount + 1}`;
205
+ // Input shape will be updated when connections are made
206
+ inputShape = 'Connect input';
207
+ outputShape = `[${nodeConfig.units}]`;
208
+ parameters = 'Connect input to calculate';
209
+ break;
210
+ case 'output':
211
+ nodeName = 'Output Layer';
212
+ inputShape = 'Connect input';
213
+ outputShape = `[${nodeConfig.units}]`;
214
+ parameters = 'Connect input to calculate';
215
+ break;
216
+ case 'conv':
217
+ const convCount = document.querySelectorAll('.canvas-node[data-type="conv"]').length;
218
+ nodeConfig.filters = 32 * (convCount + 1);
219
+ nodeName = `Conv2D ${convCount + 1}`;
220
+ inputShape = 'Connect input';
221
+ outputShape = 'Depends on input';
222
+ // Create parameter string
223
+ parameters = `Kernel: ${nodeConfig.kernelSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`;
224
+ break;
225
+ case 'pool':
226
+ const poolCount = document.querySelectorAll('.canvas-node[data-type="pool"]').length;
227
+ nodeName = `Pooling ${poolCount + 1}`;
228
+ inputShape = 'Connect input';
229
+ outputShape = 'Depends on input';
230
+ parameters = `Pool size: ${nodeConfig.poolSize.join('×')}\nStride: ${nodeConfig.strides.join('×')}\nPadding: ${nodeConfig.padding}`;
231
+ break;
232
+ default:
233
+ nodeName = 'Unknown Layer';
234
+ inputShape = 'N/A';
235
+ outputShape = 'N/A';
236
+ parameters = 'N/A';
237
+ }
238
+
239
+ // Create node content
240
+ const nodeContent = document.createElement('div');
241
+ nodeContent.className = 'node-content';
242
+
243
+ // Add shape information in a structured way
244
+ const shapeInfo = document.createElement('div');
245
+ shapeInfo.className = 'shape-info';
246
+ shapeInfo.innerHTML = `
247
+ <div class="shape-row"><span class="shape-label">Input:</span> <span class="input-shape">${inputShape}</span></div>
248
+ <div class="shape-row"><span class="shape-label">Output:</span> <span class="output-shape">${outputShape}</span></div>
249
+ `;
250
+
251
+ // Add parameters section
252
+ const paramsSection = document.createElement('div');
253
+ paramsSection.className = 'params-section';
254
+ paramsSection.innerHTML = `
255
+ <div class="params-details">${parameters}</div>
256
+ <div class="node-parameters">Params: ${nodeConfig.parameters !== undefined ? formatNumber(nodeConfig.parameters) : '?'}</div>
257
+ `;
258
+
259
+ // Assemble content
260
+ nodeContent.appendChild(shapeInfo);
261
+ nodeContent.appendChild(paramsSection);
262
+
263
+ // Add dimensions section to show shapes compactly
264
+ const dimensionsSection = document.createElement('div');
265
+ dimensionsSection.className = 'node-dimensions';
266
+
267
+ // Set dimensions text based on node type
268
+ let dimensionsText = '';
269
+ switch(nodeType) {
270
+ case 'input':
271
+ dimensionsText = nodeConfig.shape.join(' × ');
272
+ break;
273
+ case 'hidden':
274
+ case 'output':
275
+ dimensionsText = nodeConfig.units.toString();
276
+ break;
277
+ case 'conv':
278
+ if (nodeConfig.inputShape && nodeConfig.outputShape) {
279
+ dimensionsText = `${nodeConfig.inputShape.join('×')} → ${nodeConfig.outputShape.join('×')}`;
280
+ } else {
281
+ dimensionsText = `? → ${nodeConfig.filters} filters`;
282
+ }
283
+ break;
284
+ case 'pool':
285
+ if (nodeConfig.inputShape && nodeConfig.outputShape) {
286
+ dimensionsText = `${nodeConfig.inputShape.join('×')} → ${nodeConfig.outputShape.join('×')}`;
287
+ } else {
288
+ dimensionsText = `? → ?`;
289
+ }
290
+ break;
291
+ case 'linear':
292
+ dimensionsText = `${nodeConfig.inputFeatures} → ${nodeConfig.outputFeatures}`;
293
+ break;
294
+ }
295
+ dimensionsSection.textContent = dimensionsText;
296
+
297
+ // Add node title for clearer identification
298
+ const nodeTitle = document.createElement('div');
299
+ nodeTitle.className = 'node-title';
300
+ nodeTitle.textContent = nodeName;
301
+
302
+ // Add node controls (edit and delete buttons)
303
+ const nodeControls = document.createElement('div');
304
+ nodeControls.className = 'node-controls';
305
+
306
+ const editButton = document.createElement('button');
307
+ editButton.className = 'node-edit-btn';
308
+ editButton.innerHTML = '✎';
309
+ editButton.title = 'Edit Layer';
310
+
311
+ const deleteButton = document.createElement('button');
312
+ deleteButton.className = 'node-delete-btn';
313
+ deleteButton.innerHTML = '×';
314
+ deleteButton.title = 'Delete Layer';
315
+
316
+ nodeControls.appendChild(editButton);
317
+ nodeControls.appendChild(deleteButton);
318
+
319
+ // Add connection ports
320
+ const portIn = document.createElement('div');
321
+ portIn.className = 'node-port port-in';
322
+
323
+ const portOut = document.createElement('div');
324
+ portOut.className = 'node-port port-out';
325
+
326
+ // Assemble the node with the new structure
327
+ canvasNode.appendChild(nodeTitle);
328
+ canvasNode.appendChild(nodeControls);
329
+ canvasNode.appendChild(dimensionsSection);
330
+ canvasNode.appendChild(nodeContent);
331
+ canvasNode.appendChild(portIn);
332
+ canvasNode.appendChild(portOut);
333
+
334
+ // Store node data attributes for easier access
335
+ canvasNode.setAttribute('data-name', nodeName);
336
+ canvasNode.setAttribute('data-dimensions', dimensionsText);
337
+
338
+ // Add node to the canvas
339
+ canvas.appendChild(canvasNode);
340
+
341
+ // Store node configuration
342
+ canvasNode.layerConfig = nodeConfig;
343
+
344
+ // Add event listeners for node manipulation
345
+ canvasNode.addEventListener('mousedown', startDrag);
346
+
347
+ // Update port event listeners for the new class names
348
+ portIn.addEventListener('mousedown', (e) => {
349
+ e.stopPropagation();
350
+ });
351
+
352
+ portOut.addEventListener('mousedown', (e) => {
353
+ e.stopPropagation();
354
+ startConnection(canvasNode, e);
355
+ });
356
+
357
+ // Double-click to edit node properties
358
+ canvasNode.addEventListener('dblclick', () => {
359
+ openLayerEditor(canvasNode);
360
+ });
361
+
362
+ // Right-click to delete
363
+ canvasNode.addEventListener('contextmenu', (e) => {
364
+ e.preventDefault();
365
+ deleteNode(canvasNode);
366
+ });
367
+
368
+ // Add click event for edit button
369
+ editButton.addEventListener('click', (e) => {
370
+ e.stopPropagation();
371
+ openLayerEditor(canvasNode);
372
+ });
373
+
374
+ // Add click event for delete button
375
+ deleteButton.addEventListener('click', (e) => {
376
+ e.stopPropagation();
377
+ deleteNode(canvasNode);
378
+ });
379
+
380
+ // Add to network layers for architecture building
381
+ networkLayers.layers.push({
382
+ id: layerId,
383
+ type: nodeType,
384
+ name: nodeName,
385
+ position: { x: posX, y: posY },
386
+ dimensions: dimensionsText,
387
+ config: nodeConfig,
388
+ parameters: nodeConfig.parameters || 0
389
+ });
390
+
391
+ // Notify about network changes
392
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
393
+ detail: networkLayers
394
+ }));
395
+
396
+ updateConnections();
397
+
398
+ // Hide the canvas hint after adding a node
399
  const canvasHint = document.querySelector('.canvas-hint');
400
  if (canvasHint) {
401
  canvasHint.style.display = 'none';
402
  }
403
 
404
+ // Reset states and references
405
+ draggedNode = null;
406
+ recentlyCreated.inProgress = false;
407
 
408
+ // Force cleanup any stray global variables after a short delay
409
+ setTimeout(() => {
410
+ if (window.draggedNode) {
411
+ delete window.draggedNode;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  }
413
+ recentlyCreated.isDropHandled = false;
414
+ }, 100);
415
+ }
416
+
417
+ // Delete a node and its associated connections
418
+ function deleteNode(node) {
419
+ if (!node) return;
420
+
421
+ const nodeId = node.getAttribute('data-id');
422
+
423
+ // Remove all connections to/from this node
424
+ const connections = document.querySelectorAll(`.connection[data-source="${nodeId}"], .connection[data-target="${nodeId}"]`);
425
+
426
+ connections.forEach(connection => {
427
+ if (connection.parentNode) {
428
+ connection.parentNode.removeChild(connection);
429
+ }
430
+ });
431
+
432
+ // Remove from networkLayers.connections
433
+ networkLayers.connections = networkLayers.connections.filter(conn =>
434
+ conn.source !== nodeId && conn.target !== nodeId
435
+ );
436
+
437
+ // Remove from networkLayers.layers
438
+ const layerIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId);
439
+ if (layerIndex !== -1) {
440
+ networkLayers.layers.splice(layerIndex, 1);
441
+ }
442
+
443
+ // Remove the node from the DOM
444
+ if (node.parentNode) {
445
+ node.parentNode.removeChild(node);
446
+ }
447
+
448
+ // Show the canvas hint if no nodes left
449
+ if (document.querySelectorAll('.canvas-node').length === 0) {
450
+ const canvasHint = document.querySelector('.canvas-hint');
451
+ if (canvasHint) {
452
+ canvasHint.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  }
455
+
456
+ // Update connections to remove orphaned ones
457
+ updateConnections();
458
+
459
+ // Notify about network changes
460
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
461
+ detail: networkLayers
462
+ }));
463
  }
464
 
465
  // Start dragging an existing node on the canvas
466
  function startDrag(e) {
467
+ console.log('[DEBUG] startDrag called', e.target);
468
+
469
  if (isConnecting) return;
470
 
471
  // Only start drag if not clicking on buttons or ports
 
474
  }
475
 
476
  isDragging = true;
477
+ // Make sure we get the canvas-node, even if we clicked on a child element
478
  const target = e.target.closest('.canvas-node');
479
+ if (!target) {
480
+ console.error('[ERROR] No canvas-node found in startDrag');
481
+ return;
482
+ }
483
+
484
  const rect = target.getBoundingClientRect();
485
 
486
  // Calculate offset
487
  offsetX = e.clientX - rect.left;
488
  offsetY = e.clientY - rect.top;
489
 
490
+ // Add event listeners to document, not the element
491
  document.addEventListener('mousemove', dragNode);
492
  document.addEventListener('mouseup', stopDrag);
493
 
 
500
  // Add dragging class for visual feedback
501
  draggedNode.classList.add('dragging');
502
 
503
+ // Add dragging class to body for consistent cursor
504
+ document.body.classList.add('node-dragging');
505
+
506
  // Prevent default behavior
507
  e.preventDefault();
508
+
509
+ console.log(`[DEBUG] Started dragging node: ${target.getAttribute('data-id')}`);
510
  }
511
 
512
  // Drag node on the canvas
513
  function dragNode(e) {
514
+ if (!isDragging || !draggedNode) {
515
+ console.log('[WARN] dragNode called but not in dragging state');
516
+ return;
517
+ }
518
 
519
  const canvasRect = canvas.getBoundingClientRect();
520
  let x = e.clientX - canvasRect.left - offsetX;
 
541
  networkLayers.layers[layerIndex].position = { x, y };
542
  }
543
 
544
+ // Force update all connections immediately to make them responsive
545
  updateConnections();
546
  }
547
 
548
  // Stop dragging
549
+ function stopDrag(e) {
550
+ if (!isDragging) {
551
+ return;
552
+ }
553
 
554
+ console.log('[DEBUG] stopDrag called');
555
+
556
+ // Always clean up event listeners
557
  document.removeEventListener('mousemove', dragNode);
558
  document.removeEventListener('mouseup', stopDrag);
559
 
560
+ isDragging = false;
561
+
562
+ // Remove dragging class from body
563
+ document.body.classList.remove('node-dragging');
564
+
565
+ // Reset z-index and remove dragging class if node exists
566
  if (draggedNode) {
567
  draggedNode.style.zIndex = "10";
568
  draggedNode.classList.remove('dragging');
569
 
570
  // Trigger connections update one more time
571
  updateConnections();
572
+
573
+ // Clear the reference
574
+ const nodeId = draggedNode.getAttribute('data-id');
575
+ console.log(`[DEBUG] Stopped dragging node: ${nodeId}`);
576
+ draggedNode = null;
577
  }
578
  }
579
 
 
587
  connectionLine.className = 'connection temp-connection';
588
 
589
  // Get start position (center of the port)
590
+ const portOut = node.querySelector('.node-port.port-out');
591
  const portRect = portOut.getBoundingClientRect();
592
  const canvasRect = canvas.getBoundingClientRect();
593
 
 
626
  const nodeId = node.getAttribute('data-id');
627
  const isValidTarget = isValidConnection(sourceType, nodeType, sourceId, nodeId);
628
 
629
+ const portIn = node.querySelector('.node-port.port-in');
630
+ if (portIn) {
631
+ if (isValidTarget) {
632
+ portIn.classList.add('valid-target');
633
+ } else {
634
+ portIn.classList.add('invalid-target');
635
+ }
636
  }
637
  }
638
  });
 
640
 
641
  // Remove highlights from all ports
642
  function removePortHighlights() {
643
+ document.querySelectorAll('.node-port.port-in, .node-port.port-out').forEach(port => {
644
  port.classList.remove('active-port', 'valid-target', 'invalid-target');
645
  });
646
  }
 
680
  if (!isConnecting || !connectionLine) return;
681
 
682
  const canvasRect = canvas.getBoundingClientRect();
683
+ const portOut = startNode.querySelector('.node-port.port-out');
684
  const portRect = portOut.getBoundingClientRect();
685
 
686
  // Calculate start and end points
 
700
  // Highlight the port under cursor
701
  document.querySelectorAll('.canvas-node').forEach(node => {
702
  if (node !== startNode) {
703
+ const portIn = node.querySelector('.node-port.port-in');
704
+ if (portIn) {
705
+ const portInRect = portIn.getBoundingClientRect();
706
+
707
+ // Check if mouse is over the input port
708
+ if (e.clientX >= portInRect.left && e.clientX <= portInRect.right &&
709
+ e.clientY >= portInRect.top && e.clientY <= portInRect.bottom) {
710
+ portIn.classList.add('port-hover');
711
+ } else {
712
+ portIn.classList.remove('port-hover');
713
+ }
714
  }
715
  }
716
  });
 
724
  let targetNode = null;
725
  document.querySelectorAll('.canvas-node').forEach(node => {
726
  if (node !== startNode) {
727
+ const portIn = node.querySelector('.node-port.port-in');
728
+ if (portIn) {
729
+ const portRect = portIn.getBoundingClientRect();
 
 
 
 
 
 
 
 
730
 
731
+ if (e.clientX >= portRect.left && e.clientX <= portRect.right &&
732
+ e.clientY >= portRect.top && e.clientY <= portRect.bottom) {
733
+
734
+ // Check if this would be a valid connection
735
+ const sourceType = startNode.getAttribute('data-type');
736
+ const targetType = node.getAttribute('data-type');
737
+ const sourceId = startNode.getAttribute('data-id');
738
+ const targetId = node.getAttribute('data-id');
739
+
740
+ if (isValidConnection(sourceType, targetType, sourceId, targetId)) {
741
+ targetNode = node;
742
+ }
743
  }
744
  }
745
  }
 
757
 
758
  // Remove all port highlights
759
  removePortHighlights();
760
+ document.querySelectorAll('.node-port').forEach(port => {
761
  port.classList.remove('port-hover');
762
  });
763
 
 
782
 
783
  // Check if this is a valid connection
784
  if (isValidConnection(sourceType, targetType, sourceId, targetId)) {
785
+ // Remove the temporary line
786
+ if (connectionLine && connectionLine.parentNode) {
787
+ connectionLine.parentNode.removeChild(connectionLine);
788
+ }
789
 
790
+ // Create a permanent connection line
791
+ const connection = document.createElement('div');
792
+ connection.className = 'connection';
793
+ connection.setAttribute('data-source', sourceId);
794
+ connection.setAttribute('data-target', targetId);
795
 
796
+ // Add to canvas
797
+ canvas.appendChild(connection);
798
+
799
+ // Position the connection
800
+ const sourcePort = startNode.querySelector('.node-port.port-out');
801
+ const targetPort = targetNode.querySelector('.node-port.port-in');
802
+
803
+ if (sourcePort && targetPort) {
804
+ const sourceRect = sourcePort.getBoundingClientRect();
805
+ const targetRect = targetPort.getBoundingClientRect();
806
+ const canvasRect = canvas.getBoundingClientRect();
807
+
808
+ const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left;
809
+ const startY = sourceRect.top + sourceRect.height / 2 - canvasRect.top;
810
+ const endX = targetRect.left + targetRect.width / 2 - canvasRect.left;
811
+ const endY = targetRect.top + targetRect.height / 2 - canvasRect.top;
812
+
813
+ const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
814
+ const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
815
+
816
+ connection.style.left = `${startX}px`;
817
+ connection.style.top = `${startY}px`;
818
+ connection.style.width = `${length}px`;
819
+ connection.style.transform = `rotate(${angle}deg)`;
820
+ }
821
+
822
+ // Update network layers
823
+ const sourceLayerIndex = networkLayers.layers.findIndex(layer => layer.id === sourceId);
824
+ const targetLayerIndex = networkLayers.layers.findIndex(layer => layer.id === targetId);
825
+
826
+ if (sourceLayerIndex !== -1 && targetLayerIndex !== -1) {
827
+ networkLayers.connections.push({
828
+ source: sourceId,
829
+ target: targetId
830
+ });
831
+
832
+ // Initialize connections array if it doesn't exist
833
+ if (!networkLayers.layers[sourceLayerIndex].connections) {
834
+ networkLayers.layers[sourceLayerIndex].connections = [];
835
+ }
836
+ if (!networkLayers.layers[targetLayerIndex].connections) {
837
+ networkLayers.layers[targetLayerIndex].connections = [];
838
+ }
839
+
840
+ // Add connection to layers
841
+ networkLayers.layers[sourceLayerIndex].connections.push(targetId);
842
+ networkLayers.layers[targetLayerIndex].connections.push(sourceId);
843
+
844
+ // Update target node using the source node's configuration
845
+ const sourceConfig = networkLayers.layers[sourceLayerIndex].config;
846
+
847
+ if (sourceConfig && sourceConfig.outputShape) {
848
+ // Update target node with source's output shape
849
+ if (!targetNode.layerConfig) {
850
+ targetNode.layerConfig = {};
851
+ }
852
+
853
+ // Set input shape of target to output shape of source
854
+ targetNode.layerConfig.inputShape = [...sourceConfig.outputShape];
855
+
856
+ // Update parameters using our helper function
857
+ updateNodeParameters(targetNode, targetType, sourceConfig);
858
+
859
+ // Recursively update downstream nodes
860
+ updateDownstreamNodes(targetId);
861
+
862
+ // Force update all parameters in the network for complete synchronization
863
+ forceUpdateNetworkParameters();
864
+ }
865
+ }
866
+
867
+ // Notify about network changes
868
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
869
+ detail: networkLayers
870
+ }));
871
+ }
872
+
873
+ // Reset variables
874
+ isConnecting = false;
875
+ startNode = null;
876
+ connectionLine = null;
877
+
878
+ // Remove event listeners
879
+ document.removeEventListener('mousemove', drawConnection);
880
+ document.removeEventListener('mouseup', cancelConnection);
881
+ }
882
+
883
+ // Update connections when nodes are moved
884
+ function updateConnections(specificNodeId = null) {
885
+ console.log(`[DEBUG] updateConnections called ${specificNodeId ? 'for node: ' + specificNodeId : 'for all connections'}`);
886
+
887
+ // Get all connections or just those related to the specified node
888
+ let connections;
889
+ if (specificNodeId) {
890
+ connections = document.querySelectorAll(`.connection[data-source="${specificNodeId}"], .connection[data-target="${specificNodeId}"]`);
891
+ } else {
892
+ connections = document.querySelectorAll('.connection:not(.temp-connection)');
893
+ }
894
+
895
+ console.log(`[DEBUG] Updating ${connections.length} connections`);
896
+
897
+ connections.forEach(connection => {
898
+ const sourceId = connection.getAttribute('data-source');
899
+ const targetId = connection.getAttribute('data-target');
900
 
901
+ const sourceNode = document.querySelector(`.canvas-node[data-id="${sourceId}"]`);
902
+ const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`);
903
 
904
+ if (sourceNode && targetNode) {
905
+ const sourcePort = sourceNode.querySelector('.node-port.port-out');
906
+ const targetPort = targetNode.querySelector('.node-port.port-in');
907
+
908
+ if (sourcePort && targetPort) {
909
+ const canvasRect = canvas.getBoundingClientRect();
910
+ const sourceRect = sourcePort.getBoundingClientRect();
911
+ const targetRect = targetPort.getBoundingClientRect();
912
+
913
+ const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left;
914
+ const startY = sourceRect.top + sourceRect.height / 2 - canvasRect.top;
915
+ const endX = targetRect.left + targetRect.width / 2 - canvasRect.left;
916
+ const endY = targetRect.top + targetRect.height / 2 - canvasRect.top;
917
+
918
+ const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
919
+ const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
920
+
921
+ connection.style.left = `${startX}px`;
922
+ connection.style.top = `${startY}px`;
923
+ connection.style.width = `${length}px`;
924
+ connection.style.transform = `rotate(${angle}deg)`;
925
+ }
926
+ } else {
927
+ // If either node is missing, remove the connection
928
+ if (connection.parentNode) {
929
+ console.log(`[DEBUG] Removing orphaned connection between ${sourceId} and ${targetId}`);
930
+ connection.parentNode.removeChild(connection);
931
+
932
+ // Remove from the connections array
933
+ const connIndex = networkLayers.connections.findIndex(conn =>
934
+ conn.source === sourceId && conn.target === targetId
935
+ );
936
+ if (connIndex !== -1) {
937
+ networkLayers.connections.splice(connIndex, 1);
938
+ }
939
+ }
940
+ }
941
+ });
942
+ }
943
+
944
+ // Helper function to update a node's parameters and display
945
+ function updateNodeParameters(node, nodeType, sourceConfig) {
946
+ if (!node || !nodeType || !sourceConfig) return;
947
+
948
+ const nodeId = node.getAttribute('data-id');
949
+
950
+ // Ensure node's layerConfig exists
951
+ if (!node.layerConfig) {
952
+ node.layerConfig = {};
953
+ }
954
+
955
+ // Ensure input shape is set in the layer config
956
+ if (sourceConfig.outputShape) {
957
+ node.layerConfig.inputShape = [...sourceConfig.outputShape];
958
 
959
+ // For specific layer types, calculate output shape based on input shape
960
+ switch(nodeType) {
961
+ case 'hidden':
962
+ node.layerConfig.outputShape = [node.layerConfig.units];
963
+ break;
964
+ case 'output':
965
+ node.layerConfig.outputShape = [node.layerConfig.units];
966
+ break;
967
+ case 'conv':
968
+ // Call neural network module to calculate output shape
969
+ if (window.neuralNetwork && window.neuralNetwork.calculateOutputShape) {
970
+ node.layerConfig.outputShape = window.neuralNetwork.calculateOutputShape(
971
+ 'conv',
972
+ node.layerConfig.inputShape,
973
+ node.layerConfig
974
+ );
975
+ }
976
+ break;
977
+ case 'pool':
978
+ // Call neural network module to calculate output shape
979
+ if (window.neuralNetwork && window.neuralNetwork.calculateOutputShape) {
980
+ node.layerConfig.outputShape = window.neuralNetwork.calculateOutputShape(
981
+ 'pool',
982
+ node.layerConfig.inputShape,
983
+ node.layerConfig
984
+ );
985
+ }
986
+ break;
987
+ }
988
+ }
989
+
990
+ // Calculate parameters using the neural network module
991
+ let newParams = 0;
992
+
993
+ if (window.neuralNetwork && window.neuralNetwork.calculateParameters) {
994
+ newParams = window.neuralNetwork.calculateParameters(
995
+ nodeType,
996
+ node.layerConfig,
997
+ sourceConfig
998
+ );
999
+ } else {
1000
+ // Fallback parameter calculation if neuralNetwork module is not available
1001
+ switch(nodeType) {
1002
+ case 'hidden':
1003
+ if (node.layerConfig.inputShape && node.layerConfig.units) {
1004
+ // Parameters = (input_size * units) + units (weights + biases)
1005
+ const inputSize = node.layerConfig.inputShape[0];
1006
+ newParams = (inputSize * node.layerConfig.units) + node.layerConfig.units;
1007
+ }
1008
+ break;
1009
+ case 'output':
1010
+ if (node.layerConfig.inputShape && node.layerConfig.units) {
1011
+ // Parameters = (input_size * units) + units (weights + biases)
1012
+ const inputSize = node.layerConfig.inputShape[0];
1013
+ newParams = (inputSize * node.layerConfig.units) + node.layerConfig.units;
1014
+ }
1015
+ break;
1016
+ case 'conv':
1017
+ if (node.layerConfig.inputShape && node.layerConfig.filters && node.layerConfig.kernelSize) {
1018
+ // Parameters = (kernel_height * kernel_width * input_channels * filters) + filters
1019
+ const inputChannels = node.layerConfig.inputShape.length > 2 ? node.layerConfig.inputShape[2] : 1;
1020
+ newParams = (node.layerConfig.kernelSize[0] * node.layerConfig.kernelSize[1] *
1021
+ inputChannels * node.layerConfig.filters) + node.layerConfig.filters;
1022
+ }
1023
+ break;
1024
+ case 'pool':
1025
+ // Pooling layers don't have trainable parameters
1026
+ newParams = 0;
1027
+ break;
1028
+ }
1029
+ }
1030
+
1031
+ // Update parameter count in both the node object and network model
1032
+ if (newParams !== undefined) {
1033
+ // Update the node object
1034
+ node.layerConfig.parameters = newParams;
1035
+
1036
+ // Update the network model
1037
+ const layerIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId);
1038
+ if (layerIndex !== -1) {
1039
+ networkLayers.layers[layerIndex].parameters = newParams;
1040
+ if (networkLayers.layers[layerIndex].config) {
1041
+ networkLayers.layers[layerIndex].config.parameters = newParams;
1042
+
1043
+ // Also update output shape in model
1044
+ if (node.layerConfig.outputShape) {
1045
+ networkLayers.layers[layerIndex].config.outputShape = [...node.layerConfig.outputShape];
1046
+ }
1047
+ }
1048
+ }
1049
+
1050
+ // Force update the display
1051
+ const paramsDisplay = node.querySelector('.node-parameters');
1052
+ if (paramsDisplay) {
1053
+ paramsDisplay.textContent = `Params: ${formatNumber(newParams)}`;
1054
+ }
1055
+ }
1056
+
1057
+ // Update input shape display
1058
+ if (node.layerConfig.inputShape) {
1059
+ const inputShapeDisplay = node.querySelector('.input-shape');
1060
+ if (inputShapeDisplay) {
1061
+ inputShapeDisplay.textContent = `[${node.layerConfig.inputShape.join(' × ')}]`;
1062
+ }
1063
+ }
1064
+
1065
+ // Update output shape display
1066
+ if (node.layerConfig.outputShape) {
1067
+ const outputShapeDisplay = node.querySelector('.output-shape');
1068
+ if (outputShapeDisplay) {
1069
+ outputShapeDisplay.textContent = `[${node.layerConfig.outputShape.join(' × ')}]`;
1070
+ }
1071
+ }
1072
+
1073
+ // Update the dimensions display
1074
+ updateNodeDimensions(node);
1075
+
1076
+ // Force a rerender of this node to ensure all changes are displayed
1077
+ setTimeout(() => {
1078
+ // Minimal DOM update to force re-rendering
1079
+ const originalDisplay = node.style.display;
1080
+ node.style.display = 'none';
1081
+ // Force reflow
1082
+ void node.offsetHeight;
1083
+ node.style.display = originalDisplay;
1084
+ }, 10);
1085
+ }
1086
+
1087
+ // Update node dimensions display
1088
+ function updateNodeDimensions(node) {
1089
+ if (!node || !node.layerConfig) return;
1090
+
1091
+ const nodeType = node.getAttribute('data-type');
1092
+ const dimensionsSection = node.querySelector('.node-dimensions');
1093
+ if (!dimensionsSection) return;
1094
+
1095
+ let dimensionsText = '';
1096
+
1097
+ // Generate appropriate dimensions text based on node type
1098
+ switch (nodeType) {
1099
+ case 'input':
1100
+ if (node.layerConfig.shape) {
1101
+ dimensionsText = node.layerConfig.shape.join(' × ');
1102
+ }
1103
+ break;
1104
+ case 'hidden':
1105
+ case 'output':
1106
+ dimensionsText = node.layerConfig.units ? node.layerConfig.units.toString() : '?';
1107
+ break;
1108
+ case 'conv':
1109
+ if (node.layerConfig.inputShape && node.layerConfig.outputShape) {
1110
+ dimensionsText = `${node.layerConfig.inputShape.join('×')} → ${node.layerConfig.outputShape.join('×')}`;
1111
+ } else if (node.layerConfig.filters) {
1112
+ dimensionsText = `? → ${node.layerConfig.filters} filters`;
1113
+ }
1114
+ break;
1115
+ case 'pool':
1116
+ if (node.layerConfig.inputShape && node.layerConfig.outputShape) {
1117
+ dimensionsText = `${node.layerConfig.inputShape.join('×')} → ${node.layerConfig.outputShape.join('×')}`;
1118
+ } else {
1119
+ dimensionsText = `? → ?`;
1120
+ }
1121
+ break;
1122
+ case 'linear':
1123
+ if (node.layerConfig.inputFeatures && node.layerConfig.outputFeatures) {
1124
+ dimensionsText = `${node.layerConfig.inputFeatures} → ${node.layerConfig.outputFeatures}`;
1125
+ }
1126
+ break;
1127
+ }
1128
+
1129
+ if (dimensionsText) {
1130
+ dimensionsSection.textContent = dimensionsText;
1131
+ node.setAttribute('data-dimensions', dimensionsText);
1132
+ }
1133
+ }
1134
+
1135
+ // Recursively update nodes downstream from the given node ID
1136
+ function updateDownstreamNodes(nodeId) {
1137
+ // Get all connections that start from this node
1138
+ const outgoingConnections = networkLayers.connections.filter(conn => conn.source === nodeId);
1139
+
1140
+ outgoingConnections.forEach(conn => {
1141
+ const targetId = conn.target;
1142
+ const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`);
1143
+ const sourceNode = document.querySelector(`.canvas-node[data-id="${nodeId}"]`);
1144
+
1145
+ if (targetNode && sourceNode) {
1146
+ const targetType = targetNode.getAttribute('data-type');
1147
+ const sourceType = sourceNode.getAttribute('data-type');
1148
+
1149
+ // Skip if source or target type is invalid
1150
+ if (!targetType || !sourceType) return;
1151
+
1152
+ // Find the indices in the layers array
1153
+ const sourceIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId);
1154
+ const targetIndex = networkLayers.layers.findIndex(layer => layer.id === targetId);
1155
+
1156
+ if (sourceIndex !== -1 && targetIndex !== -1) {
1157
+ const sourceConfig = networkLayers.layers[sourceIndex].config;
1158
+
1159
+ // Update the target node with the source's output shape
1160
+ if (sourceConfig && sourceConfig.outputShape) {
1161
+ // Set input shape of target
1162
+ if (!targetNode.layerConfig) {
1163
+ targetNode.layerConfig = {};
1164
+ }
1165
+
1166
+ targetNode.layerConfig.inputShape = [...sourceConfig.outputShape];
1167
+ networkLayers.layers[targetIndex].config.inputShape = [...sourceConfig.outputShape];
1168
+
1169
+ // Update parameters
1170
+ updateNodeParameters(targetNode, targetType, sourceConfig);
1171
+
1172
+ // Continue updating downstream
1173
+ updateDownstreamNodes(targetId);
1174
+ }
1175
+ }
1176
+ }
1177
+ });
1178
+ }
1179
+
1180
+ // Force update all network connections and parameters
1181
+ function forceUpdateNetworkParameters() {
1182
+ // First, identify root nodes (nodes with no incoming connections)
1183
+ const targetIds = new Set(networkLayers.connections.map(conn => conn.target));
1184
+ const rootNodeIds = networkLayers.layers
1185
+ .filter(layer => !targetIds.has(layer.id))
1186
+ .map(layer => layer.id);
1187
+
1188
+ // Update from each root node
1189
+ rootNodeIds.forEach(nodeId => {
1190
+ updateDownstreamNodes(nodeId);
1191
+ });
1192
+
1193
+ // After updating all parameters, notify about the network changes
1194
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
1195
+ detail: networkLayers
1196
+ }));
1197
+ }
1198
+
1199
+ // Get the current network architecture
1200
+ function getNetworkArchitecture() {
1201
+ return networkLayers;
1202
+ }
1203
+
1204
+ // Clear all nodes from the canvas
1205
+ function clearAllNodes() {
1206
+ // Clear all nodes and connections
1207
+ document.querySelectorAll('.canvas-node, .connection').forEach(el => {
1208
+ el.parentNode.removeChild(el);
1209
+ });
1210
+
1211
+ // Reset network layers
1212
+ networkLayers = {
1213
+ layers: [],
1214
+ connections: []
1215
+ };
1216
+
1217
+ // Reset layer counter
1218
+ window.neuralNetwork.resetLayerCounter();
1219
+
1220
+ // Show the canvas hint
1221
+ const canvasHint = document.querySelector('.canvas-hint');
1222
+ if (canvasHint) {
1223
+ canvasHint.style.display = 'block';
1224
+ }
1225
+
1226
+ // Trigger network updated event
1227
+ const event = new CustomEvent('networkUpdated', { detail: networkLayers });
1228
+ document.dispatchEvent(event);
1229
+ }
1230
+
1231
+ // Open layer editor modal
1232
+ function openLayerEditor(node) {
1233
+ if (!node) return;
1234
+
1235
+ const nodeId = node.getAttribute('data-id');
1236
+ const nodeType = node.getAttribute('data-type');
1237
+ const nodeName = node.getAttribute('data-name');
1238
+ const dimensions = node.getAttribute('data-dimensions');
1239
+
1240
+ // Trigger custom event with the node object
1241
+ const event = new CustomEvent('openLayerEditor', {
1242
+ detail: {
1243
+ id: nodeId,
1244
+ type: nodeType,
1245
+ name: nodeName,
1246
+ dimensions: dimensions,
1247
+ node: node // Pass the node object
1248
+ }
1249
+ });
1250
+ document.dispatchEvent(event);
1251
+ }
1252
+
1253
+ // Create SVG container for connections
1254
+ function createSVGContainer() {
1255
+ const svgContainer = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
1256
+ svgContainer.classList.add('svg-container');
1257
+ svgContainer.style.position = 'absolute';
1258
+ svgContainer.style.top = '0';
1259
+ svgContainer.style.left = '0';
1260
+ svgContainer.style.width = '100%';
1261
+ svgContainer.style.height = '100%';
1262
+ svgContainer.style.pointerEvents = 'none';
1263
+ svgContainer.style.zIndex = '5';
1264
+ canvas.appendChild(svgContainer);
1265
+ return svgContainer;
1266
+ }
1267
+
1268
+ // Export functions
1269
+ window.dragDrop = {
1270
+ getNetworkArchitecture,
1271
+ clearAllNodes,
1272
+ updateConnections
1273
+ };
1274
+
1275
+ // Expose the drag functions to the window for debugging
1276
+ window.startDrag = startDrag;
1277
+ window.dragNode = dragNode;
1278
+ window.stopDrag = stopDrag;
1279
+ window.deleteNode = deleteNode;
1280
+ }
js/drag-fix.js ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Direct drag fix for neural network playground
2
+ // This uses a direct implementation to bypass any issues with the existing drag code
3
+
4
+ (function() {
5
+ console.log('Loading direct drag fix...');
6
+
7
+ // Wait for DOM to be ready
8
+ document.addEventListener('DOMContentLoaded', function() {
9
+ // Wait a bit longer to make sure other scripts have initialized
10
+ setTimeout(initializeDragFix, 1000);
11
+ });
12
+
13
+ function initializeDragFix() {
14
+ console.log('Initializing direct drag fix');
15
+
16
+ // Track drag state
17
+ let activeNode = null;
18
+ let offsetX = 0;
19
+ let offsetY = 0;
20
+ let isDragging = false;
21
+
22
+ // Get the canvas
23
+ const canvas = document.getElementById('network-canvas');
24
+ if (!canvas) {
25
+ console.error('Cannot find canvas element');
26
+ return;
27
+ }
28
+
29
+ // Function to add drag handlers to a node
30
+ function addDragHandlers(node) {
31
+ console.log(`Adding direct drag handlers to node: ${node.getAttribute('data-id') || 'unknown'}`);
32
+
33
+ // Use mousedown event to initiate drag
34
+ node.addEventListener('mousedown', function(e) {
35
+ // Only handle direct clicks on the node or its title/content, not on controls or ports
36
+ if (e.target.closest('.node-controls') || e.target.closest('.node-port')) {
37
+ return;
38
+ }
39
+
40
+ console.log('Direct mousedown on node', e.target);
41
+
42
+ // Initialize drag
43
+ activeNode = node;
44
+ const rect = node.getBoundingClientRect();
45
+ offsetX = e.clientX - rect.left;
46
+ offsetY = e.clientY - rect.top;
47
+ isDragging = true;
48
+
49
+ // Add visual indication
50
+ node.classList.add('dragging');
51
+ document.body.classList.add('node-dragging');
52
+ node.style.zIndex = '1000';
53
+
54
+ // Prevent text selection and other default behaviors
55
+ e.preventDefault();
56
+ });
57
+ }
58
+
59
+ // Global mouse handlers for drag
60
+ document.addEventListener('mousemove', function(e) {
61
+ if (!isDragging || !activeNode) return;
62
+
63
+ // Log occasionally to avoid flooding console
64
+ if (Math.random() < 0.05) {
65
+ console.log('%c✓ DRAGGING IS WORKING!', 'background: #4CAF50; color: white; padding: 2px 5px; border-radius: 3px;');
66
+ }
67
+
68
+ const canvasRect = canvas.getBoundingClientRect();
69
+ let x = e.clientX - canvasRect.left - offsetX;
70
+ let y = e.clientY - canvasRect.top - offsetY;
71
+
72
+ // Ensure node stays within canvas
73
+ const nodeWidth = activeNode.offsetWidth || 180;
74
+ const nodeHeight = activeNode.offsetHeight || 120;
75
+
76
+ x = Math.max(0, Math.min(canvasRect.width - nodeWidth, x));
77
+ y = Math.max(0, Math.min(canvasRect.height - nodeHeight, y));
78
+
79
+ // Move the node
80
+ activeNode.style.left = `${x}px`;
81
+ activeNode.style.top = `${y}px`;
82
+
83
+ // Update connections if function exists
84
+ if (window.dragDrop && typeof window.dragDrop.updateConnections === 'function') {
85
+ const nodeId = activeNode.getAttribute('data-id');
86
+ window.dragDrop.updateConnections(nodeId);
87
+ }
88
+
89
+ // Update data model if function exists
90
+ if (window.updateNodePositionInModel) {
91
+ window.updateNodePositionInModel(activeNode, x, y);
92
+ }
93
+ });
94
+
95
+ document.addEventListener('mouseup', function() {
96
+ if (!isDragging || !activeNode) return;
97
+
98
+ console.log('Direct mouseup - ending drag');
99
+
100
+ // Remove visual indication
101
+ activeNode.classList.remove('dragging');
102
+ document.body.classList.remove('node-dragging');
103
+ activeNode.style.zIndex = '10';
104
+
105
+ // Clean up
106
+ isDragging = false;
107
+ activeNode = null;
108
+
109
+ // Update all connections
110
+ if (window.dragDrop && typeof window.dragDrop.updateConnections === 'function') {
111
+ window.dragDrop.updateConnections();
112
+ }
113
+
114
+ // Dispatch event to notify other components
115
+ const event = new CustomEvent('nodeDragEnd');
116
+ document.dispatchEvent(event);
117
+ });
118
+
119
+ // MutationObserver to add drag handlers to new nodes
120
+ const observer = new MutationObserver(function(mutations) {
121
+ mutations.forEach(function(mutation) {
122
+ if (mutation.type === 'childList') {
123
+ mutation.addedNodes.forEach(function(node) {
124
+ if (node.nodeType === 1 && node.classList.contains('canvas-node')) {
125
+ addDragHandlers(node);
126
+ }
127
+ });
128
+ }
129
+ });
130
+ });
131
+
132
+ // Start observing the canvas for added nodes
133
+ observer.observe(canvas, { childList: true });
134
+
135
+ // Add handlers to existing nodes
136
+ document.querySelectorAll('.canvas-node').forEach(addDragHandlers);
137
+
138
+ // Expose a helper function to update node positions in the model
139
+ window.updateNodePositionInModel = function(node, x, y) {
140
+ if (!window.dragDrop || !window.dragDrop.getNetworkArchitecture) return;
141
+
142
+ const nodeId = node.getAttribute('data-id');
143
+ const networkLayers = window.dragDrop.getNetworkArchitecture();
144
+
145
+ const layerIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId);
146
+ if (layerIndex !== -1) {
147
+ networkLayers.layers[layerIndex].position = { x, y };
148
+ }
149
+ };
150
+
151
+ console.log('Direct drag fix initialized');
152
+ }
153
+ })();
js/forward-propagation.js ADDED
@@ -0,0 +1,713 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Forward Propagation Animation
2
+ document.addEventListener('DOMContentLoaded', () => {
3
+ // Set initialization flag
4
+ window.forwardPropInitialized = true;
5
+ console.log('Forward propagation script initialized');
6
+
7
+ // Canvas initialization function
8
+ function initializeCanvas() {
9
+ console.log('Initializing forward propagation canvas');
10
+ const canvas = document.getElementById('forward-canvas');
11
+ if (!canvas) {
12
+ console.error('Forward propagation canvas not found!');
13
+ return;
14
+ }
15
+
16
+ const ctx = canvas.getContext('2d');
17
+ if (!ctx) {
18
+ console.error('Could not get 2D context for forward propagation canvas');
19
+ return;
20
+ }
21
+
22
+ // Set canvas dimensions
23
+ const container = canvas.parentElement;
24
+ if (container) {
25
+ canvas.width = container.clientWidth || 800;
26
+ canvas.height = container.clientHeight || 400;
27
+ } else {
28
+ canvas.width = 800;
29
+ canvas.height = 400;
30
+ }
31
+
32
+ // Clear canvas
33
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
34
+
35
+ // Reset animation state and redraw
36
+ resetAnimation();
37
+ drawNetwork();
38
+ }
39
+
40
+ // Register the canvas initialization function with tab manager
41
+ if (typeof window !== 'undefined') {
42
+ window.initForwardPropCanvas = initializeCanvas;
43
+ }
44
+
45
+ // Canvas and context
46
+ const canvas = document.getElementById('forward-canvas');
47
+ const ctx = canvas.getContext('2d');
48
+
49
+ // Control elements
50
+ const startButton = document.getElementById('start-forward-animation');
51
+ const pauseButton = document.getElementById('pause-forward-animation');
52
+ const resetButton = document.getElementById('reset-forward-animation');
53
+ const inputSelector = document.getElementById('input-selector');
54
+
55
+ // Display elements
56
+ const currentLayerText = document.getElementById('current-layer');
57
+ const forwardDescription = document.getElementById('forward-description');
58
+ const computationValues = document.getElementById('computation-values');
59
+
60
+ // Animation state
61
+ let animationState = {
62
+ running: false,
63
+ currentLayer: 0, // 0: input, 1: hidden, 2: output
64
+ currentNeuron: -1, // -1 means all neurons in the layer are being processed
65
+ network: null,
66
+ animationFrameId: null,
67
+ lastTimestamp: 0,
68
+ speed: 3, // Speed of animation
69
+ highlightedConnections: []
70
+ };
71
+
72
+ // Neuron states
73
+ const INACTIVE = 0;
74
+ const COMPUTING = 1;
75
+ const ACTIVATED = 2;
76
+
77
+ // Neural network class for visualization
78
+ class ForwardNetwork {
79
+ constructor() {
80
+ // Architecture: 3 input neurons, 4 hidden neurons with ReLU, 2 output neurons with sigmoid
81
+ this.layers = [
82
+ { neurons: 3, activation: 'none', name: 'Input' },
83
+ { neurons: 4, activation: 'relu', name: 'Hidden' },
84
+ { neurons: 2, activation: 'sigmoid', name: 'Output' }
85
+ ];
86
+
87
+ // Generate random weights and biases
88
+ this.weights = [
89
+ this.generateRandomWeights(3, 4), // Input to Hidden
90
+ this.generateRandomWeights(4, 2) // Hidden to Output
91
+ ];
92
+
93
+ this.biases = [
94
+ Array(4).fill(0).map(() => Math.random() * 0.4 - 0.2), // Hidden layer biases
95
+ Array(2).fill(0).map(() => Math.random() * 0.4 - 0.2) // Output layer biases
96
+ ];
97
+
98
+ // Neuron values - inputs, weighted sums (z), and activations (a)
99
+ this.inputs = [
100
+ [0.8, 0.2, 0.5], // Default input values
101
+ Array(4).fill(0), // Hidden layer
102
+ Array(2).fill(0) // Output layer
103
+ ];
104
+
105
+ this.weightedSums = [
106
+ Array(3).fill(0), // Input layer doesn't have weighted sums
107
+ Array(4).fill(0), // Hidden layer weighted sums
108
+ Array(2).fill(0) // Output layer weighted sums
109
+ ];
110
+
111
+ this.activations = [
112
+ Array(3).fill(0), // Input layer activations are just the inputs
113
+ Array(4).fill(0), // Hidden layer activations
114
+ Array(2).fill(0) // Output layer activations
115
+ ];
116
+
117
+ // Neuron states for animation
118
+ this.neuronStates = [
119
+ Array(3).fill(INACTIVE), // Input layer neuron states
120
+ Array(4).fill(INACTIVE), // Hidden layer neuron states
121
+ Array(2).fill(INACTIVE) // Output layer neuron states
122
+ ];
123
+
124
+ // Computation details for display
125
+ this.currentComputation = {
126
+ layer: 0,
127
+ neuron: 0,
128
+ inputs: [],
129
+ weights: [],
130
+ weightedSum: 0,
131
+ bias: 0,
132
+ activation: 0
133
+ };
134
+ }
135
+
136
+ // Generate random weights
137
+ generateRandomWeights(inputSize, outputSize) {
138
+ const weights = [];
139
+ for (let i = 0; i < inputSize * outputSize; i++) {
140
+ weights.push(Math.random() * 0.4 - 0.2); // Random between -0.2 and 0.2
141
+ }
142
+ return weights;
143
+ }
144
+
145
+ // ReLU activation function
146
+ relu(x) {
147
+ return Math.max(0, x);
148
+ }
149
+
150
+ // Sigmoid activation function
151
+ sigmoid(x) {
152
+ return 1 / (1 + Math.exp(-x));
153
+ }
154
+
155
+ // Set input values
156
+ setInputs(inputs) {
157
+ this.inputs[0] = [...inputs];
158
+ this.activations[0] = [...inputs]; // For input layer, activations = inputs
159
+
160
+ // Reset all neuron states and other layers' values
161
+ for (let layer = 0; layer < this.layers.length; layer++) {
162
+ this.neuronStates[layer] = Array(this.layers[layer].neurons).fill(INACTIVE);
163
+
164
+ if (layer > 0) {
165
+ this.inputs[layer] = Array(this.layers[layer].neurons).fill(0);
166
+ this.weightedSums[layer] = Array(this.layers[layer].neurons).fill(0);
167
+ this.activations[layer] = Array(this.layers[layer].neurons).fill(0);
168
+ }
169
+ }
170
+ }
171
+
172
+ // Compute a single neuron
173
+ computeNeuron(layer, neuron) {
174
+ if (layer === 0) {
175
+ // Input layer neurons are already set directly
176
+ this.neuronStates[layer][neuron] = ACTIVATED;
177
+ return;
178
+ }
179
+
180
+ // Get inputs from previous layer
181
+ const prevLayerActivations = this.activations[layer - 1];
182
+
183
+ // Compute weighted sum
184
+ let weightedSum = this.biases[layer - 1][neuron];
185
+ const weights = [];
186
+ const inputs = [];
187
+
188
+ for (let i = 0; i < this.layers[layer - 1].neurons; i++) {
189
+ const weightIdx = i * this.layers[layer].neurons + neuron;
190
+ const weight = this.weights[layer - 1][weightIdx];
191
+ const input = prevLayerActivations[i];
192
+
193
+ weights.push(weight);
194
+ inputs.push(input);
195
+ weightedSum += weight * input;
196
+ }
197
+
198
+ // Store weighted sum
199
+ this.weightedSums[layer][neuron] = weightedSum;
200
+
201
+ // Apply activation function
202
+ let activation;
203
+ if (this.layers[layer].activation === 'relu') {
204
+ activation = this.relu(weightedSum);
205
+ } else if (this.layers[layer].activation === 'sigmoid') {
206
+ activation = this.sigmoid(weightedSum);
207
+ } else {
208
+ activation = weightedSum; // Linear/no activation
209
+ }
210
+
211
+ // Store activation
212
+ this.activations[layer][neuron] = activation;
213
+
214
+ // Store computation details for display
215
+ this.currentComputation = {
216
+ layer,
217
+ neuron,
218
+ inputs,
219
+ weights,
220
+ weightedSum,
221
+ bias: this.biases[layer - 1][neuron],
222
+ activation
223
+ };
224
+
225
+ // Update neuron state
226
+ this.neuronStates[layer][neuron] = ACTIVATED;
227
+ }
228
+
229
+ // Reset the network
230
+ reset() {
231
+ // Reset all neuron states
232
+ for (let layer = 0; layer < this.layers.length; layer++) {
233
+ this.neuronStates[layer] = Array(this.layers[layer].neurons).fill(INACTIVE);
234
+
235
+ if (layer > 0) {
236
+ this.weightedSums[layer] = Array(this.layers[layer].neurons).fill(0);
237
+ this.activations[layer] = Array(this.layers[layer].neurons).fill(0);
238
+ }
239
+ }
240
+
241
+ // Set input layer activations to inputs
242
+ this.activations[0] = [...this.inputs[0]];
243
+ }
244
+ }
245
+
246
+ // Canvas resize functionality
247
+ function resizeCanvas() {
248
+ const container = canvas.parentElement;
249
+ canvas.width = container.clientWidth;
250
+ canvas.height = container.clientHeight;
251
+
252
+ // Redraw if already animating
253
+ if (animationState.network) {
254
+ drawNetwork(animationState.network);
255
+ }
256
+ }
257
+
258
+ // Initialize the visualization
259
+ function initVisualization() {
260
+ if (!canvas) return;
261
+
262
+ resizeCanvas();
263
+ window.addEventListener('resize', resizeCanvas);
264
+
265
+ // Create neural network
266
+ animationState.network = new ForwardNetwork();
267
+
268
+ // Set initial inputs
269
+ if (inputSelector) {
270
+ const selectedInput = inputSelector.value;
271
+ switch(selectedInput) {
272
+ case 'sample1':
273
+ animationState.network.setInputs([0.8, 0.2, 0.5]);
274
+ break;
275
+ case 'sample2':
276
+ animationState.network.setInputs([0.1, 0.9, 0.3]);
277
+ break;
278
+ case 'sample3':
279
+ animationState.network.setInputs([0.5, 0.5, 0.5]);
280
+ break;
281
+ default:
282
+ animationState.network.setInputs([0.8, 0.2, 0.5]);
283
+ }
284
+ }
285
+
286
+ // Initialize neuron states for input layer
287
+ animationState.network.neuronStates[0] = Array(animationState.network.layers[0].neurons).fill(ACTIVATED);
288
+
289
+ // Draw initial state
290
+ drawNetwork(animationState.network);
291
+
292
+ // Update computation display
293
+ updateComputationDisplay(animationState.network);
294
+
295
+ // Set button states
296
+ startButton.disabled = false;
297
+ pauseButton.disabled = true;
298
+ resetButton.disabled = true;
299
+ }
300
+
301
+ // Draw the network
302
+ function drawNetwork(network) {
303
+ if (!ctx) return;
304
+
305
+ // Clear canvas
306
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
307
+
308
+ const padding = 50;
309
+ const width = canvas.width - padding * 2;
310
+ const height = canvas.height - padding * 2;
311
+
312
+ // Calculate neuron positions
313
+ const layers = network.layers;
314
+ const layerPositions = [];
315
+
316
+ for (let i = 0; i < layers.length; i++) {
317
+ const layerNeurons = [];
318
+ const x = padding + (width / (layers.length - 1)) * i;
319
+
320
+ for (let j = 0; j < layers[i].neurons; j++) {
321
+ const y = padding + (height / (layers[i].neurons + 1)) * (j + 1);
322
+ layerNeurons.push({ x, y });
323
+ }
324
+
325
+ layerPositions.push(layerNeurons);
326
+ }
327
+
328
+ // Draw connections
329
+ for (let layerIdx = 0; layerIdx < layers.length - 1; layerIdx++) {
330
+ for (let i = 0; i < layers[layerIdx].neurons; i++) {
331
+ for (let j = 0; j < layers[layerIdx + 1].neurons; j++) {
332
+ const weightIdx = i * layers[layerIdx + 1].neurons + j;
333
+ const weight = network.weights[layerIdx][weightIdx];
334
+
335
+ // Normalize weight for visualization
336
+ const normalizedWeight = Math.min(Math.abs(weight) * 5, 1);
337
+
338
+ // Check if this connection is highlighted
339
+ const isHighlighted = animationState.highlightedConnections.some(
340
+ conn => conn.layer === layerIdx && conn.from === i && conn.to === j
341
+ );
342
+
343
+ // Set connection color based on state
344
+ let connectionColor;
345
+ if (isHighlighted) {
346
+ connectionColor = `rgba(46, 204, 113, ${normalizedWeight + 0.2})`;
347
+ ctx.lineWidth = 3;
348
+ } else if (network.neuronStates[layerIdx][i] === ACTIVATED &&
349
+ network.neuronStates[layerIdx + 1][j] === ACTIVATED) {
350
+ connectionColor = `rgba(52, 152, 219, ${normalizedWeight})`;
351
+ ctx.lineWidth = 2;
352
+ } else if (network.neuronStates[layerIdx][i] === ACTIVATED) {
353
+ connectionColor = `rgba(52, 152, 219, ${normalizedWeight * 0.5})`;
354
+ ctx.lineWidth = 1.5;
355
+ } else {
356
+ connectionColor = `rgba(200, 200, 200, ${normalizedWeight * 0.3})`;
357
+ ctx.lineWidth = 1;
358
+ }
359
+
360
+ // Draw the connection
361
+ ctx.beginPath();
362
+ ctx.moveTo(layerPositions[layerIdx][i].x, layerPositions[layerIdx][i].y);
363
+ ctx.lineTo(layerPositions[layerIdx + 1][j].x, layerPositions[layerIdx + 1][j].y);
364
+ ctx.strokeStyle = connectionColor;
365
+ ctx.stroke();
366
+ }
367
+ }
368
+ }
369
+
370
+ // Draw neurons
371
+ for (let layerIdx = 0; layerIdx < layers.length; layerIdx++) {
372
+ for (let i = 0; i < layers[layerIdx].neurons; i++) {
373
+ const { x, y } = layerPositions[layerIdx][i];
374
+
375
+ // Get neuron activation and state
376
+ const activation = network.activations[layerIdx][i];
377
+ const neuronState = network.neuronStates[layerIdx][i];
378
+
379
+ // Set neuron color based on state and activation
380
+ let neuronColor;
381
+ if (neuronState === COMPUTING) {
382
+ neuronColor = 'rgba(241, 196, 15, 0.9)'; // Yellow for computing
383
+ } else if (neuronState === ACTIVATED) {
384
+ neuronColor = `rgba(52, 152, 219, ${Math.min(Math.max(activation, 0.3), 0.9)})`;
385
+ } else {
386
+ neuronColor = 'rgba(200, 200, 200, 0.5)'; // Grey for inactive
387
+ }
388
+
389
+ // Draw neuron
390
+ ctx.beginPath();
391
+ ctx.arc(x, y, 20, 0, Math.PI * 2);
392
+ ctx.fillStyle = neuronColor;
393
+ ctx.fill();
394
+ ctx.strokeStyle = '#2980b9';
395
+ ctx.lineWidth = 2;
396
+ ctx.stroke();
397
+
398
+ // Draw neuron value
399
+ ctx.fillStyle = '#fff';
400
+ ctx.font = '12px Arial';
401
+ ctx.textAlign = 'center';
402
+ ctx.textBaseline = 'middle';
403
+
404
+ if (layerIdx === 0 || neuronState === ACTIVATED) {
405
+ // Show activation for activated neurons
406
+ ctx.fillText(activation.toFixed(2), x, y);
407
+ } else {
408
+ // Show ? for inactive neurons
409
+ ctx.fillText('?', x, y);
410
+ }
411
+
412
+ // Draw layer labels
413
+ if (i === 0) {
414
+ ctx.fillStyle = '#333';
415
+ ctx.font = '14px Arial';
416
+ ctx.textAlign = 'center';
417
+ ctx.fillText(layers[layerIdx].name, x, y - 40);
418
+
419
+ // Highlight current layer being processed
420
+ if (layerIdx === animationState.currentLayer) {
421
+ ctx.beginPath();
422
+ ctx.arc(x, y - 40, 5, 0, Math.PI * 2);
423
+ ctx.fillStyle = '#e74c3c';
424
+ ctx.fill();
425
+ }
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ // Update computation display
432
+ function updateComputationDisplay(network) {
433
+ if (!computationValues) return;
434
+
435
+ const currentLayer = animationState.currentLayer;
436
+ const currentNeuron = animationState.currentNeuron;
437
+
438
+ // Update current layer text
439
+ if (currentLayerText) {
440
+ currentLayerText.textContent = network.layers[currentLayer].name;
441
+ }
442
+
443
+ // Update description
444
+ if (forwardDescription) {
445
+ if (currentLayer === 0) {
446
+ forwardDescription.textContent = "Input values are passed directly to the first layer.";
447
+ } else if (currentNeuron === -1) {
448
+ forwardDescription.textContent = `All neurons in the ${network.layers[currentLayer].name} layer compute their activations.`;
449
+ } else {
450
+ const activationType = network.layers[currentLayer].activation;
451
+ forwardDescription.textContent = `Computing neuron ${currentNeuron + 1} in the ${network.layers[currentLayer].name} layer using ${activationType.toUpperCase()} activation.`;
452
+ }
453
+ }
454
+
455
+ // Update computation values
456
+ if (currentLayer === 0 || currentNeuron === -1) {
457
+ // Show layer summary
458
+ let html = '';
459
+
460
+ if (currentLayer === 0) {
461
+ html += '<div class="computation-group">Input Layer Values:</div>';
462
+ for (let i = 0; i < network.layers[0].neurons; i++) {
463
+ html += `<div>Input ${i + 1}: ${network.activations[0][i].toFixed(4)}</div>`;
464
+ }
465
+ } else {
466
+ html += `<div class="computation-group">${network.layers[currentLayer].name} Layer Summary:</div>`;
467
+ for (let i = 0; i < network.layers[currentLayer].neurons; i++) {
468
+ const z = network.weightedSums[currentLayer][i];
469
+ const a = network.activations[currentLayer][i];
470
+ html += `<div>Neuron ${i + 1}: z = ${z.toFixed(4)}, a = ${a.toFixed(4)}</div>`;
471
+ }
472
+ }
473
+
474
+ computationValues.innerHTML = html;
475
+ } else {
476
+ // Show specific neuron computation
477
+ const comp = network.currentComputation;
478
+ let html = `<div class="computation-group">Computation for ${network.layers[comp.layer].name} Layer, Neuron ${comp.neuron + 1}:</div>`;
479
+
480
+ // Weighted sum calculation
481
+ html += '<div class="computation-row">Weighted Sum (z) = bias';
482
+ for (let i = 0; i < comp.inputs.length; i++) {
483
+ html += ` + (${comp.weights[i].toFixed(3)} × ${comp.inputs[i].toFixed(3)})`;
484
+ }
485
+ html += `</div>`;
486
+ html += `<div>z = ${comp.bias.toFixed(3)}`;
487
+ for (let i = 0; i < comp.inputs.length; i++) {
488
+ const product = comp.weights[i] * comp.inputs[i];
489
+ html += ` + ${product.toFixed(3)}`;
490
+ }
491
+ html += ` = ${comp.weightedSum.toFixed(4)}</div>`;
492
+
493
+ // Activation calculation
494
+ const activationType = network.layers[comp.layer].activation;
495
+ html += `<div class="computation-row">Activation (a) = ${activationType}(z)</div>`;
496
+
497
+ if (activationType === 'relu') {
498
+ html += `<div>a = max(0, ${comp.weightedSum.toFixed(4)}) = ${comp.activation.toFixed(4)}</div>`;
499
+ } else if (activationType === 'sigmoid') {
500
+ html += `<div>a = 1 / (1 + e<sup>-${comp.weightedSum.toFixed(4)}</sup>) = ${comp.activation.toFixed(4)}</div>`;
501
+ }
502
+
503
+ computationValues.innerHTML = html;
504
+ }
505
+ }
506
+
507
+ // Animation loop
508
+ function animate(timestamp) {
509
+ if (!animationState.running) return;
510
+
511
+ // Calculate delta time based on speed
512
+ const deltaTime = timestamp - animationState.lastTimestamp;
513
+ const interval = 2000 / animationState.speed; // Base interval divided by speed
514
+
515
+ if (deltaTime > interval || animationState.lastTimestamp === 0) {
516
+ animationState.lastTimestamp = timestamp;
517
+
518
+ const network = animationState.network;
519
+ const currentLayer = animationState.currentLayer;
520
+ const currentNeuron = animationState.currentNeuron;
521
+
522
+ // Clear highlighted connections
523
+ animationState.highlightedConnections = [];
524
+
525
+ if (currentLayer === 0) {
526
+ // Move to first neuron of hidden layer
527
+ animationState.currentLayer = 1;
528
+ animationState.currentNeuron = 0;
529
+
530
+ // Set hidden layer neuron state to computing
531
+ network.neuronStates[1][0] = COMPUTING;
532
+
533
+ // Highlight connections from input to this neuron
534
+ for (let i = 0; i < network.layers[0].neurons; i++) {
535
+ animationState.highlightedConnections.push({
536
+ layer: 0,
537
+ from: i,
538
+ to: 0
539
+ });
540
+ }
541
+ } else {
542
+ if (currentNeuron < network.layers[currentLayer].neurons - 1) {
543
+ // Compute current neuron
544
+ network.computeNeuron(currentLayer, currentNeuron);
545
+
546
+ // Move to next neuron in this layer
547
+ animationState.currentNeuron = currentNeuron + 1;
548
+
549
+ // Set next neuron state to computing
550
+ network.neuronStates[currentLayer][currentNeuron + 1] = COMPUTING;
551
+
552
+ // Highlight connections from previous layer to next neuron
553
+ for (let i = 0; i < network.layers[currentLayer - 1].neurons; i++) {
554
+ animationState.highlightedConnections.push({
555
+ layer: currentLayer - 1,
556
+ from: i,
557
+ to: currentNeuron + 1
558
+ });
559
+ }
560
+ } else {
561
+ // Compute last neuron in current layer
562
+ network.computeNeuron(currentLayer, currentNeuron);
563
+
564
+ // Check if we've reached the output layer
565
+ if (currentLayer < network.layers.length - 1) {
566
+ // Move to first neuron of next layer
567
+ animationState.currentLayer = currentLayer + 1;
568
+ animationState.currentNeuron = 0;
569
+
570
+ // Set next layer's first neuron state to computing
571
+ network.neuronStates[currentLayer + 1][0] = COMPUTING;
572
+
573
+ // Highlight connections from current layer to next layer's first neuron
574
+ for (let i = 0; i < network.layers[currentLayer].neurons; i++) {
575
+ animationState.highlightedConnections.push({
576
+ layer: currentLayer,
577
+ from: i,
578
+ to: 0
579
+ });
580
+ }
581
+ } else {
582
+ // We've finished the entire forward pass
583
+ // Pause animation and show the complete result
584
+ pauseAnimation();
585
+
586
+ // Set current layer to output layer with no specific neuron
587
+ animationState.currentLayer = currentLayer;
588
+ animationState.currentNeuron = -1;
589
+ }
590
+ }
591
+ }
592
+
593
+ // Update visualization
594
+ drawNetwork(network);
595
+ updateComputationDisplay(network);
596
+ }
597
+
598
+ // Continue animation
599
+ animationState.animationFrameId = requestAnimationFrame(animate);
600
+ }
601
+
602
+ // Start animation
603
+ function startAnimation() {
604
+ if (!animationState.running) {
605
+ animationState.running = true;
606
+ animationState.lastTimestamp = 0;
607
+ animationState.animationFrameId = requestAnimationFrame(animate);
608
+
609
+ startButton.disabled = true;
610
+ pauseButton.disabled = false;
611
+ resetButton.disabled = false;
612
+ }
613
+ }
614
+
615
+ // Pause animation
616
+ function pauseAnimation() {
617
+ if (animationState.running) {
618
+ animationState.running = false;
619
+ if (animationState.animationFrameId) {
620
+ cancelAnimationFrame(animationState.animationFrameId);
621
+ }
622
+
623
+ startButton.disabled = false;
624
+ pauseButton.disabled = true;
625
+ resetButton.disabled = false;
626
+ }
627
+ }
628
+
629
+ // Reset animation
630
+ function resetAnimation() {
631
+ pauseAnimation();
632
+
633
+ // Reset network state
634
+ animationState.network.reset();
635
+
636
+ // Reset animation state
637
+ animationState.currentLayer = 0;
638
+ animationState.currentNeuron = -1;
639
+ animationState.highlightedConnections = [];
640
+
641
+ // Mark input layer neurons as activated
642
+ for (let i = 0; i < animationState.network.layers[0].neurons; i++) {
643
+ animationState.network.neuronStates[0][i] = ACTIVATED;
644
+ }
645
+
646
+ // Update visualization
647
+ drawNetwork(animationState.network);
648
+ updateComputationDisplay(animationState.network);
649
+
650
+ startButton.disabled = false;
651
+ pauseButton.disabled = true;
652
+ resetButton.disabled = false;
653
+ }
654
+
655
+ // Handle input selection change
656
+ function handleInputChange() {
657
+ if (!inputSelector || !animationState.network) return;
658
+
659
+ const selectedInput = inputSelector.value;
660
+ let newInputs;
661
+
662
+ switch(selectedInput) {
663
+ case 'sample1':
664
+ newInputs = [0.8, 0.2, 0.5];
665
+ break;
666
+ case 'sample2':
667
+ newInputs = [0.1, 0.9, 0.3];
668
+ break;
669
+ case 'sample3':
670
+ newInputs = [0.5, 0.5, 0.5];
671
+ break;
672
+ default:
673
+ newInputs = [0.8, 0.2, 0.5];
674
+ }
675
+
676
+ // Set new inputs and reset
677
+ animationState.network.setInputs(newInputs);
678
+ resetAnimation();
679
+ }
680
+
681
+ // Set up event listeners
682
+ function setupEventListeners() {
683
+ if (startButton) {
684
+ startButton.addEventListener('click', startAnimation);
685
+ }
686
+
687
+ if (pauseButton) {
688
+ pauseButton.addEventListener('click', pauseAnimation);
689
+ }
690
+
691
+ if (resetButton) {
692
+ resetButton.addEventListener('click', resetAnimation);
693
+ }
694
+
695
+ if (inputSelector) {
696
+ inputSelector.addEventListener('change', handleInputChange);
697
+ }
698
+
699
+ // Tab switching event from the main tab controller
700
+ document.addEventListener('tabSwitch', (e) => {
701
+ if (e.detail.tab === 'forward-propagation') {
702
+ // Initialize or reset when switching to this tab
703
+ resetAnimation();
704
+ }
705
+ });
706
+ }
707
+
708
+ // Initialize the visualization
709
+ initVisualization();
710
+
711
+ // Set up event listeners
712
+ setupEventListeners();
713
+ });
js/layer-editor.js ADDED
@@ -0,0 +1,912 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Layer Editor for Neural Network Playground
3
+ * Handles editing of layer parameters through a modal interface
4
+ */
5
+
6
+ (function() {
7
+ console.log('Loading layer editor...');
8
+
9
+ // Run immediately, don't wait for DOMContentLoaded
10
+ initializeLayerEditor();
11
+
12
+ function initializeLayerEditor() {
13
+ // Get modal elements
14
+ const modal = document.getElementById('layer-editor-modal');
15
+ const form = modal.querySelector('.layer-form');
16
+ const saveButton = modal.querySelector('.save-layer-btn');
17
+ const closeButtons = modal.querySelectorAll('.close-modal');
18
+ const modalTitle = modal.querySelector('.modal-title');
19
+
20
+ if (!modal || !form) {
21
+ console.error('Layer editor modal elements not found!');
22
+ return;
23
+ }
24
+
25
+ // Current node being edited
26
+ let currentNode = null;
27
+ let currentConfig = null;
28
+
29
+ // DEBUG: Log when script is loaded
30
+ console.log('Layer editor initialized, waiting for openLayerEditor events', modal);
31
+
32
+ // Listen for clicks on edit buttons directly
33
+ document.addEventListener('click', function(e) {
34
+ // Check if click was on an edit button
35
+ if (e.target.classList.contains('node-edit-btn')) {
36
+ e.preventDefault();
37
+ e.stopPropagation();
38
+
39
+ // Find the node
40
+ const node = e.target.closest('.canvas-node');
41
+ if (!node) {
42
+ console.error('Could not find node for edit button');
43
+ return;
44
+ }
45
+
46
+ // Get node info
47
+ const nodeId = node.getAttribute('data-id');
48
+ const nodeType = node.getAttribute('data-type');
49
+ const nodeName = node.getAttribute('data-name') || node.querySelector('.node-title').textContent;
50
+
51
+ console.log('Edit button clicked for node', nodeId, nodeType);
52
+
53
+ // Store reference to current node and its config
54
+ currentNode = node;
55
+ currentConfig = node.layerConfig || {};
56
+
57
+ // Update modal title
58
+ modalTitle.textContent = `Edit ${nodeName || 'Layer'}`;
59
+
60
+ // Generate form based on node type
61
+ generateFormFields(form, nodeType, currentConfig);
62
+
63
+ // Show modal with debuggable visible class
64
+ modal.style.display = 'block';
65
+ modal.setAttribute('data-visible', 'true');
66
+
67
+ // Force reflow to ensure display takes effect
68
+ void modal.offsetWidth;
69
+
70
+ // Add active class for transition
71
+ modal.classList.add('active');
72
+ }
73
+ });
74
+
75
+ // Also listen for the openLayerEditor event (fallback)
76
+ document.addEventListener('openLayerEditor', function(e) {
77
+ const detail = e.detail;
78
+
79
+ if (!detail || !detail.node) {
80
+ console.error('Invalid layer editor data', detail);
81
+ return;
82
+ }
83
+
84
+ console.log('openLayerEditor event received:', detail);
85
+
86
+ // Store reference to current node and its config
87
+ currentNode = detail.node;
88
+ currentConfig = currentNode.layerConfig || {};
89
+
90
+ // Update modal title
91
+ modalTitle.textContent = `Edit ${detail.name || 'Layer'}`;
92
+
93
+ // Generate form based on node type
94
+ generateFormFields(form, detail.type, currentConfig);
95
+
96
+ // Show modal
97
+ modal.style.display = 'block';
98
+ modal.setAttribute('data-visible', 'true');
99
+
100
+ // Force reflow to ensure display takes effect
101
+ void modal.offsetWidth;
102
+
103
+ // Add active class for transition
104
+ modal.classList.add('active');
105
+
106
+ console.log('Opened layer editor for', detail.id, detail.type);
107
+ });
108
+
109
+ // Close modal when clicking close button or outside the modal
110
+ closeButtons.forEach(button => {
111
+ button.addEventListener('click', function() {
112
+ modal.classList.remove('active');
113
+ setTimeout(() => {
114
+ modal.style.display = 'none';
115
+ modal.removeAttribute('data-visible');
116
+ }, 300); // Match transition duration from CSS
117
+ });
118
+ });
119
+
120
+ window.addEventListener('click', function(e) {
121
+ if (e.target === modal) {
122
+ modal.classList.remove('active');
123
+ setTimeout(() => {
124
+ modal.style.display = 'none';
125
+ modal.removeAttribute('data-visible');
126
+ }, 300);
127
+ }
128
+ });
129
+
130
+ // Handle form submission
131
+ saveButton.addEventListener('click', function() {
132
+ if (!currentNode || !currentConfig) {
133
+ console.error('No node selected for editing');
134
+ modal.classList.remove('active');
135
+ setTimeout(() => {
136
+ modal.style.display = 'none';
137
+ modal.removeAttribute('data-visible');
138
+ }, 300);
139
+ return;
140
+ }
141
+
142
+ // Get updated values from form
143
+ const formData = new FormData(form);
144
+ const updatedConfig = { ...currentConfig };
145
+
146
+ // Update config based on node type
147
+ const nodeType = currentNode.getAttribute('data-type');
148
+
149
+ // Process form data
150
+ for (let [key, value] of formData.entries()) {
151
+ // Handle arrays (from comma-separated values)
152
+ if (key.endsWith('[]') && typeof value === 'string') {
153
+ const arrayKey = key.slice(0, -2);
154
+ // Parse array values more carefully - ensuring we get numbers
155
+ const values = value.split(',')
156
+ .map(v => {
157
+ const parsed = parseFloat(v.trim());
158
+ return isNaN(parsed) ? 0 : parsed; // Convert NaN to 0
159
+ });
160
+ updatedConfig[arrayKey] = values;
161
+ console.log(`Parsed array for ${arrayKey}:`, values);
162
+ }
163
+ // Convert numeric values - more aggressively ensure integers for specific fields
164
+ else if (key === 'filters' || key === 'units') {
165
+ updatedConfig[key] = parseInt(value) || (key === 'filters' ? 32 : key === 'units' ? 64 : 0);
166
+ console.log(`Parsed ${key} as integer:`, updatedConfig[key]);
167
+ }
168
+ // Other numeric values
169
+ else if (!isNaN(value) && value !== '') {
170
+ updatedConfig[key] = parseFloat(value);
171
+ }
172
+ // Everything else as-is
173
+ else {
174
+ updatedConfig[key] = value;
175
+ }
176
+ }
177
+
178
+ console.log('Updated config:', updatedConfig);
179
+
180
+ // Update node with new config
181
+ updateNodeWithConfig(currentNode, nodeType, updatedConfig);
182
+
183
+ // Close modal
184
+ modal.classList.remove('active');
185
+ setTimeout(() => {
186
+ modal.style.display = 'none';
187
+ modal.removeAttribute('data-visible');
188
+ }, 300);
189
+
190
+ // Clear references
191
+ currentNode = null;
192
+ currentConfig = null;
193
+ });
194
+
195
+ console.log('Layer editor initialized and listeners attached');
196
+ }
197
+
198
+ /**
199
+ * Generate form fields based on node type
200
+ */
201
+ function generateFormFields(form, nodeType, config) {
202
+ // Clear existing form
203
+ form.innerHTML = '';
204
+
205
+ console.log('Generating form fields for', nodeType, 'with config', config);
206
+
207
+ // Add output shape field to all node types
208
+ const currentOutputShape = (config.outputShape || []).join(',');
209
+
210
+ switch (nodeType) {
211
+ case 'input':
212
+ addFormField(form, 'Shape', 'shape[]', (config.shape || [28, 28, 1]).join(','), 'The input dimensions (e.g., 28,28,1 for MNIST images)');
213
+ addFormField(form, 'Output Shape', 'outputShape[]', currentOutputShape, 'Manual override for output shape (normally matches input shape)');
214
+ break;
215
+
216
+ case 'hidden':
217
+ addFormField(form, 'Units', 'units', config.units || 128, 'Number of neurons in this layer');
218
+ addFormField(form, 'Activation', 'activation', config.activation || 'relu', 'Activation function', 'select', {
219
+ options: ['relu', 'sigmoid', 'tanh', 'leaky_relu', 'linear']
220
+ });
221
+ addFormField(form, 'Output Shape', 'outputShape[]', currentOutputShape, 'Manual override for output shape (normally [units])');
222
+ break;
223
+
224
+ case 'output':
225
+ addFormField(form, 'Units', 'units', config.units || 10, 'Number of output neurons (e.g., 10 for MNIST)');
226
+ addFormField(form, 'Activation', 'activation', config.activation || 'softmax', 'Activation function', 'select', {
227
+ options: ['softmax', 'sigmoid', 'linear']
228
+ });
229
+ addFormField(form, 'Output Shape', 'outputShape[]', currentOutputShape, 'Manual override for output shape (normally [units])');
230
+ break;
231
+
232
+ case 'conv':
233
+ addFormField(form, 'Filters', 'filters', config.filters || 32, 'Number of filters (output channels)');
234
+ addFormField(form, 'Kernel Size', 'kernelSize[]', (config.kernelSize || [3, 3]).join(','), 'Size of the convolution kernel (e.g., 3,3)');
235
+ addFormField(form, 'Strides', 'strides[]', (config.strides || [1, 1]).join(','), 'Stride of the convolution (e.g., 1,1)');
236
+ addFormField(form, 'Padding', 'padding', config.padding || 'same', 'Padding method', 'select', {
237
+ options: ['same', 'valid']
238
+ });
239
+ addFormField(form, 'Activation', 'activation', config.activation || 'relu', 'Activation function', 'select', {
240
+ options: ['relu', 'sigmoid', 'tanh', 'leaky_relu', 'linear']
241
+ });
242
+ addFormField(form, 'Output Shape', 'outputShape[]', currentOutputShape, 'Manual override for calculated output shape');
243
+ break;
244
+
245
+ case 'pool':
246
+ addFormField(form, 'Pool Size', 'poolSize[]', (config.poolSize || [2, 2]).join(','), 'Size of the pooling window (e.g., 2,2)');
247
+ addFormField(form, 'Strides', 'strides[]', (config.strides || [2, 2]).join(','), 'Stride of the pooling operation (e.g., 2,2)');
248
+ addFormField(form, 'Padding', 'padding', config.padding || 'valid', 'Padding method', 'select', {
249
+ options: ['same', 'valid']
250
+ });
251
+ addFormField(form, 'Pool Type', 'poolType', config.poolType || 'max', 'Type of pooling', 'select', {
252
+ options: ['max', 'average']
253
+ });
254
+ addFormField(form, 'Output Shape', 'outputShape[]', currentOutputShape, 'Manual override for calculated output shape');
255
+ break;
256
+
257
+ case 'lstm':
258
+ addFormField(form, 'Units', 'units', config.units || 64, 'Number of LSTM units');
259
+ addFormField(form, 'Return Sequences', 'returnSequences', config.returnSequences !== false ? 'true' : 'false', 'Return the full sequence or just the final state', 'select', {
260
+ options: ['true', 'false']
261
+ });
262
+ addFormField(form, 'Activation', 'activation', config.activation || 'tanh', 'Activation function', 'select', {
263
+ options: ['tanh', 'relu', 'sigmoid']
264
+ });
265
+ addFormField(form, 'Recurrent Activation', 'recurrentActivation', config.recurrentActivation || 'sigmoid', 'Recurrent activation function', 'select', {
266
+ options: ['sigmoid', 'tanh', 'relu']
267
+ });
268
+ addFormField(form, 'Use Bias', 'useBias', config.useBias !== false ? 'true' : 'false', 'Include bias terms', 'select', {
269
+ options: ['true', 'false']
270
+ });
271
+ addFormField(form, 'Output Shape', 'outputShape[]', currentOutputShape, 'Manual override for calculated output shape');
272
+ break;
273
+
274
+ case 'rnn':
275
+ addFormField(form, 'Units', 'units', config.units || 32, 'Number of RNN units');
276
+ addFormField(form, 'Return Sequences', 'returnSequences', config.returnSequences !== false ? 'true' : 'false', 'Return the full sequence or just the final state', 'select', {
277
+ options: ['true', 'false']
278
+ });
279
+ addFormField(form, 'Activation', 'activation', config.activation || 'tanh', 'Activation function', 'select', {
280
+ options: ['tanh', 'relu', 'sigmoid']
281
+ });
282
+ addFormField(form, 'Use Bias', 'useBias', config.useBias !== false ? 'true' : 'false', 'Include bias terms', 'select', {
283
+ options: ['true', 'false']
284
+ });
285
+ addFormField(form, 'Output Shape', 'outputShape[]', currentOutputShape, 'Manual override for calculated output shape');
286
+ break;
287
+
288
+ case 'gru':
289
+ addFormField(form, 'Units', 'units', config.units || 48, 'Number of GRU units');
290
+ addFormField(form, 'Return Sequences', 'returnSequences', config.returnSequences !== false ? 'true' : 'false', 'Return the full sequence or just the final state', 'select', {
291
+ options: ['true', 'false']
292
+ });
293
+ addFormField(form, 'Activation', 'activation', config.activation || 'tanh', 'Activation function', 'select', {
294
+ options: ['tanh', 'relu', 'sigmoid']
295
+ });
296
+ addFormField(form, 'Recurrent Activation', 'recurrentActivation', config.recurrentActivation || 'sigmoid', 'Recurrent activation function', 'select', {
297
+ options: ['sigmoid', 'tanh', 'relu']
298
+ });
299
+ addFormField(form, 'Use Bias', 'useBias', config.useBias !== false ? 'true' : 'false', 'Include bias terms', 'select', {
300
+ options: ['true', 'false']
301
+ });
302
+ addFormField(form, 'Output Shape', 'outputShape[]', currentOutputShape, 'Manual override for calculated output shape');
303
+ break;
304
+
305
+ default:
306
+ form.innerHTML = '<p>No editable parameters for this layer type.</p>';
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Add a form field to the form
312
+ */
313
+ function addFormField(form, label, name, value, helpText, type = 'text', options = {}) {
314
+ const fieldContainer = document.createElement('div');
315
+ fieldContainer.className = 'form-field';
316
+
317
+ const labelElem = document.createElement('label');
318
+ labelElem.textContent = label;
319
+ labelElem.setAttribute('for', name);
320
+
321
+ let inputElem;
322
+
323
+ if (type === 'select') {
324
+ inputElem = document.createElement('select');
325
+ inputElem.name = name;
326
+ inputElem.id = name;
327
+
328
+ if (options.options) {
329
+ options.options.forEach(option => {
330
+ const optionElem = document.createElement('option');
331
+ optionElem.value = option;
332
+ optionElem.textContent = option;
333
+
334
+ if (option === value) {
335
+ optionElem.selected = true;
336
+ }
337
+
338
+ inputElem.appendChild(optionElem);
339
+ });
340
+ }
341
+ } else {
342
+ inputElem = document.createElement('input');
343
+ inputElem.type = type;
344
+ inputElem.name = name;
345
+ inputElem.id = name;
346
+ inputElem.value = value;
347
+
348
+ if (options.min !== undefined) inputElem.min = options.min;
349
+ if (options.max !== undefined) inputElem.max = options.max;
350
+ if (options.step !== undefined) inputElem.step = options.step;
351
+ }
352
+
353
+ const helpElem = document.createElement('small');
354
+ helpElem.className = 'help-text';
355
+ helpElem.textContent = helpText;
356
+
357
+ fieldContainer.appendChild(labelElem);
358
+ fieldContainer.appendChild(inputElem);
359
+ fieldContainer.appendChild(helpElem);
360
+
361
+ form.appendChild(fieldContainer);
362
+ }
363
+
364
+ /**
365
+ * Update node with new configuration
366
+ */
367
+ function updateNodeWithConfig(node, nodeType, config) {
368
+ if (!node) {
369
+ console.error('Cannot update node: Node is null');
370
+ return;
371
+ }
372
+
373
+ console.log(`Starting to update node ${node.getAttribute('data-id')} of type ${nodeType}`, config);
374
+
375
+ // Store updated config on the node
376
+ node.layerConfig = { ...config }; // Create a copy to avoid reference issues
377
+
378
+ // Get node elements
379
+ const nodeId = node.getAttribute('data-id');
380
+ const inputShapeDisplay = node.querySelector('.input-shape');
381
+ const outputShapeDisplay = node.querySelector('.output-shape');
382
+ const paramsDisplay = node.querySelector('.node-parameters');
383
+ const dimensionsDisplay = node.querySelector('.node-dimensions');
384
+ const paramsDetailsDisplay = node.querySelector('.params-details');
385
+
386
+ // Debug check
387
+ if (!inputShapeDisplay || !outputShapeDisplay || !paramsDisplay) {
388
+ console.warn('Some node displays not found:', {
389
+ inputShapeDisplay,
390
+ outputShapeDisplay,
391
+ paramsDisplay
392
+ });
393
+ }
394
+
395
+ // Handle manual output shape override first
396
+ let manualOutputShape = null;
397
+ if (config.outputShape && Array.isArray(config.outputShape) && config.outputShape.length > 0
398
+ && config.outputShape.some(dim => dim !== '?' && dim !== '')) {
399
+ // User has provided a manual output shape
400
+ manualOutputShape = [...config.outputShape];
401
+ console.log('Manual output shape provided:', manualOutputShape);
402
+ }
403
+
404
+ // Update output shape and parameters
405
+ let outputShape = manualOutputShape || config.outputShape;
406
+ let parameters = config.parameters;
407
+ let inputShape = config.inputShape;
408
+
409
+ console.log('Before calculating: outputShape =', outputShape, 'parameters =', parameters);
410
+
411
+ // Get connections to find input shape if not present
412
+ if (!inputShape && window.dragDrop && window.dragDrop.getNetworkArchitecture) {
413
+ const networkLayers = window.dragDrop.getNetworkArchitecture();
414
+ const connections = networkLayers.connections || [];
415
+ const targetsThisNode = connections.filter(conn => conn.target === nodeId);
416
+
417
+ if (targetsThisNode.length > 0) {
418
+ // Find the source node's output shape
419
+ const sourceId = targetsThisNode[0].source;
420
+ const sourceLayer = networkLayers.layers.find(layer => layer.id === sourceId);
421
+ if (sourceLayer && sourceLayer.config && sourceLayer.config.outputShape) {
422
+ inputShape = [...sourceLayer.config.outputShape];
423
+ config.inputShape = inputShape;
424
+ console.log('Found input shape from connections:', inputShape);
425
+ }
426
+ }
427
+ }
428
+
429
+ // Try to calculate new output shape and parameters only if manual output shape is not provided
430
+ if (!manualOutputShape && window.neuralNetwork) {
431
+ console.log('Using neural network module to calculate shapes and parameters');
432
+ if (window.neuralNetwork.calculateOutputShape) {
433
+ try {
434
+ const newOutputShape = window.neuralNetwork.calculateOutputShape(config, nodeType);
435
+ if (newOutputShape) {
436
+ outputShape = newOutputShape;
437
+ config.outputShape = newOutputShape;
438
+ console.log('Calculated output shape:', outputShape);
439
+ }
440
+ } catch (error) {
441
+ console.error('Error calculating output shape:', error);
442
+ }
443
+ }
444
+
445
+ if (window.neuralNetwork.calculateParameters) {
446
+ try {
447
+ const newParameters = window.neuralNetwork.calculateParameters(config, nodeType);
448
+ if (newParameters !== undefined) {
449
+ parameters = newParameters;
450
+ config.parameters = newParameters;
451
+ console.log('Calculated parameters:', parameters);
452
+ }
453
+ } catch (error) {
454
+ console.error('Error calculating parameters:', error);
455
+ }
456
+ }
457
+ } else if (!manualOutputShape) {
458
+ // Perform basic calculations based on node type only if manual shape isn't provided
459
+ console.log('Falling back to basic parameter calculations');
460
+
461
+ switch (nodeType) {
462
+ case 'input':
463
+ if (!manualOutputShape) {
464
+ outputShape = config.shape;
465
+ }
466
+ parameters = 0;
467
+ break;
468
+
469
+ case 'hidden':
470
+ const units = parseInt(config.units) || 128;
471
+ if (!manualOutputShape) {
472
+ outputShape = [units];
473
+ }
474
+ if (inputShape) {
475
+ const inputSize = inputShape.reduce((a, b) => a * b, 1);
476
+ parameters = inputSize * units + units; // weights + biases
477
+ console.log(`Hidden layer params: ${inputSize} inputs × ${units} units + ${units} biases = ${parameters}`);
478
+ } else {
479
+ console.log('No input shape available for hidden layer parameter calculation');
480
+ parameters = units; // Just biases if we don't know input size
481
+ }
482
+ break;
483
+
484
+ case 'output':
485
+ const outUnits = parseInt(config.units) || 10;
486
+ if (!manualOutputShape) {
487
+ outputShape = [outUnits];
488
+ }
489
+ if (inputShape) {
490
+ const inputSize = inputShape.reduce((a, b) => a * b, 1);
491
+ parameters = inputSize * outUnits + outUnits; // weights + biases
492
+ console.log(`Output layer params: ${inputSize} inputs × ${outUnits} units + ${outUnits} biases = ${parameters}`);
493
+ } else {
494
+ console.log('No input shape available for output layer parameter calculation');
495
+ parameters = outUnits; // Just biases if we don't know input size
496
+ }
497
+ break;
498
+
499
+ case 'conv':
500
+ if (inputShape && inputShape.length >= 3 && !manualOutputShape) {
501
+ // Very explicit type conversion - ensure all values are numbers
502
+ const height = Math.max(1, parseInt(inputShape[0]) || 1); // Ensure at least 1
503
+ const width = Math.max(1, parseInt(inputShape[1]) || 1); // Ensure at least 1
504
+ const channels = Math.max(1, parseInt(inputShape[2]) || 1); // Ensure at least 1
505
+
506
+ console.log(`Conv2D INPUT SHAPE debug: [${height}, ${width}, ${channels}]`,
507
+ {original: inputShape, parsed: [height, width, channels]});
508
+
509
+ // Ensure filters is a positive number
510
+ const filters = Math.max(1, parseInt(config.filters) || 32);
511
+
512
+ // Explicit processing of kernelSize with safety checks
513
+ let kernelSize = [3, 3]; // Default fallback
514
+ if (config.kernelSize) {
515
+ if (typeof config.kernelSize === 'string') {
516
+ kernelSize = config.kernelSize.split(',')
517
+ .map(v => Math.max(1, parseInt(v.trim()) || 1)); // Ensure at least 1
518
+ } else if (Array.isArray(config.kernelSize)) {
519
+ kernelSize = config.kernelSize
520
+ .map(v => Math.max(1, parseInt(v) || 1)); // Ensure at least 1
521
+ }
522
+ }
523
+
524
+ // Explicit processing of strides with safety checks
525
+ let strides = [1, 1]; // Default fallback
526
+ if (config.strides) {
527
+ if (typeof config.strides === 'string') {
528
+ strides = config.strides.split(',')
529
+ .map(v => Math.max(1, parseInt(v.trim()) || 1)); // Ensure at least 1
530
+ } else if (Array.isArray(config.strides)) {
531
+ strides = config.strides
532
+ .map(v => Math.max(1, parseInt(v) || 1)); // Ensure at least 1
533
+ }
534
+ }
535
+
536
+ // Ensure we have at least 2 elements for kernelSize and strides and all values are at least 1
537
+ kernelSize = kernelSize.length >= 2 ?
538
+ [Math.max(1, kernelSize[0]), Math.max(1, kernelSize[1])] :
539
+ [Math.max(1, kernelSize[0] || 3), Math.max(1, kernelSize[0] || 3)];
540
+
541
+ strides = strides.length >= 2 ?
542
+ [Math.max(1, strides[0]), Math.max(1, strides[1])] :
543
+ [Math.max(1, strides[0] || 1), Math.max(1, strides[0] || 1)];
544
+
545
+ console.log(`Conv2D CONFIG debug:`, {
546
+ filters: filters,
547
+ kernelSize: kernelSize,
548
+ strides: strides
549
+ });
550
+
551
+ // Store cleaned values back in config
552
+ config.filters = filters;
553
+ config.kernelSize = kernelSize;
554
+ config.strides = strides;
555
+
556
+ const padding = config.padding || 'same';
557
+
558
+ // Calculate output dimensions based on padding
559
+ let outHeight, outWidth;
560
+ if (padding === 'same') {
561
+ outHeight = Math.ceil(height / strides[0]);
562
+ outWidth = Math.ceil(width / strides[1]);
563
+ } else { // 'valid' padding
564
+ outHeight = Math.ceil((height - kernelSize[0] + 1) / strides[0]);
565
+ outWidth = Math.ceil((width - kernelSize[1] + 1) / strides[1]);
566
+ }
567
+
568
+ // Ensure output dimensions are at least 1
569
+ outHeight = Math.max(1, outHeight);
570
+ outWidth = Math.max(1, outWidth);
571
+
572
+ // Final output shape
573
+ outputShape = [outHeight, outWidth, filters];
574
+
575
+ // Calculate parameters step by step to avoid any overflow or multiplication errors
576
+ const kh = Number(kernelSize[0]);
577
+ const kw = Number(kernelSize[1]);
578
+ const c = Number(channels);
579
+ const f = Number(filters);
580
+
581
+ // Check for any zeros or negative values that would make the calculation invalid
582
+ if (kh <= 0 || kw <= 0 || c <= 0 || f <= 0) {
583
+ console.error(`Invalid Conv2D parameter values: kh=${kh}, kw=${kw}, c=${c}, f=${f}`);
584
+ parameters = 0;
585
+ } else {
586
+ // Calculate with explicit steps to avoid any overflow
587
+ const kernelParams = kh * kw * c * f;
588
+ const biasParams = f;
589
+ parameters = kernelParams + biasParams;
590
+
591
+ console.log(`Conv2D CALCULATION STEPS:
592
+ Kernel height (kh) = ${kh}
593
+ Kernel width (kw) = ${kw}
594
+ Input channels (c) = ${c}
595
+ Filters (f) = ${f}
596
+ Kernel params = ${kh} × ${kw} × ${c} × ${f} = ${kernelParams}
597
+ Bias params = ${biasParams}
598
+ Total params = ${kernelParams} + ${biasParams} = ${parameters}
599
+ `);
600
+ }
601
+
602
+ console.log(`Conv2D output shape: ${outHeight}×${outWidth}×${filters}`);
603
+ } else {
604
+ console.log('Cannot calculate Conv2D parameters - invalid input shape or manual shape provided:', inputShape);
605
+ if (!manualOutputShape) {
606
+ const filters = parseInt(config.filters) || 32;
607
+ outputShape = ['?', '?', filters];
608
+ }
609
+ parameters = 0; // Set to 0 instead of '?' to avoid display issues
610
+ }
611
+ break;
612
+
613
+ case 'pool':
614
+ if (inputShape && inputShape.length >= 3 && !manualOutputShape) {
615
+ const [height, width, channels] = inputShape;
616
+ const poolSize = config.poolSize || [2, 2];
617
+ const stride = config.strides || poolSize;
618
+ const padding = config.padding || 'valid';
619
+
620
+ // Calculate output dimensions
621
+ let outHeight, outWidth;
622
+ if (padding === 'same') {
623
+ outHeight = Math.ceil(height / stride[0]);
624
+ outWidth = Math.ceil(width / stride[1]);
625
+ } else { // 'valid' padding
626
+ outHeight = Math.ceil((height - poolSize[0] + 1) / stride[0]);
627
+ outWidth = Math.ceil((width - poolSize[1] + 1) / stride[1]);
628
+ }
629
+
630
+ outputShape = [outHeight, outWidth, channels];
631
+ parameters = 0; // Pooling layers have no parameters
632
+ console.log('Pooling layer has 0 parameters');
633
+ } else {
634
+ console.log('Cannot calculate pooling output shape without proper input shape or manual shape provided');
635
+ if (!manualOutputShape) {
636
+ outputShape = ['?', '?', '?'];
637
+ }
638
+ parameters = 0;
639
+ }
640
+ break;
641
+
642
+ case 'linear':
643
+ const linearUnits = parseInt(config.units) || 1;
644
+ if (!manualOutputShape) {
645
+ outputShape = [linearUnits];
646
+ }
647
+ if (inputShape) {
648
+ const inputSize = inputShape.reduce((a, b) => a * b, 1);
649
+ const useBias = config.useBias !== 'false';
650
+ parameters = inputSize * linearUnits + (useBias ? linearUnits : 0);
651
+ console.log(`Linear layer params: ${inputSize} inputs × ${linearUnits} units + ${useBias ? linearUnits : 0} biases = ${parameters}`);
652
+ } else {
653
+ console.log('No input shape available for linear layer parameter calculation');
654
+ parameters = linearUnits; // Just biases if we don't know input size
655
+ }
656
+ break;
657
+ }
658
+ }
659
+
660
+ // Make sure we have the output shape in the config
661
+ if (outputShape) {
662
+ config.outputShape = outputShape;
663
+ }
664
+
665
+ // Updated detailed parameter description
666
+ let paramsDetails = '';
667
+ switch (nodeType) {
668
+ case 'hidden':
669
+ paramsDetails = `Units: ${config.units}<br>Activation: ${config.activation || 'relu'}`;
670
+ break;
671
+ case 'output':
672
+ paramsDetails = `Units: ${config.units}<br>Activation: ${config.activation || 'softmax'}`;
673
+ break;
674
+ case 'conv':
675
+ paramsDetails = `Filters: ${config.filters}<br>Kernel: ${(config.kernelSize || [3, 3]).join('×')}<br>Strides: ${(config.strides || [1, 1]).join('×')}<br>Padding: ${config.padding || 'same'}`;
676
+ break;
677
+ case 'pool':
678
+ paramsDetails = `Pool size: ${(config.poolSize || [2, 2]).join('×')}<br>Strides: ${(config.strides || [2, 2]).join('×')}<br>Padding: ${config.padding || 'valid'}<br>Type: ${config.poolType || 'max'}`;
679
+ break;
680
+ case 'input':
681
+ paramsDetails = `Shape: ${(config.shape || [28, 28, 1]).join('×')}`;
682
+ break;
683
+ case 'linear':
684
+ paramsDetails = `Units: ${config.units}<br>Use Bias: ${config.useBias !== 'false' ? 'Yes' : 'No'}`;
685
+ break;
686
+ }
687
+
688
+ // Update displays
689
+ if (outputShape && outputShapeDisplay) {
690
+ outputShapeDisplay.textContent = `[${Array.isArray(outputShape) ? outputShape.join(' × ') : outputShape}]`;
691
+ // Highlight the output shape to show it's been updated
692
+ const originalBackground = outputShapeDisplay.style.backgroundColor;
693
+ outputShapeDisplay.style.backgroundColor = '#f0f9ff';
694
+ setTimeout(() => {
695
+ outputShapeDisplay.style.backgroundColor = originalBackground;
696
+ }, 500);
697
+ console.log('Updated output shape display with', outputShape);
698
+ }
699
+
700
+ if (inputShape && inputShapeDisplay) {
701
+ inputShapeDisplay.textContent = `[${Array.isArray(inputShape) ? inputShape.join(' × ') : inputShape}]`;
702
+ console.log('Updated input shape display');
703
+ } else if (inputShapeDisplay && nodeType !== 'input') {
704
+ inputShapeDisplay.textContent = 'Connect input';
705
+ }
706
+
707
+ // Ensure parameters is always a number for display
708
+ if (parameters !== undefined) {
709
+ if (typeof parameters === 'string') {
710
+ if (parameters === '?') {
711
+ parameters = 0;
712
+ } else {
713
+ // Try to parse it as a number
714
+ parameters = parseInt(parameters) || 0;
715
+ }
716
+ }
717
+
718
+ // Debug log with type information
719
+ console.log(`Parameter display value: ${parameters} (${typeof parameters})`);
720
+
721
+ if (paramsDisplay) {
722
+ // Special display for Conv2D
723
+ if (nodeType === 'conv') {
724
+ // Store the numeric value in the model
725
+ config.parameters = parameters;
726
+
727
+ // Format for display
728
+ const displayValue = formatNumber(parameters);
729
+ paramsDisplay.textContent = `Params: ${displayValue}`;
730
+ console.log(`Updated Conv2D parameters display: ${displayValue}`);
731
+
732
+ // Change background color briefly to indicate update
733
+ const originalColor = paramsDisplay.style.backgroundColor;
734
+ paramsDisplay.style.backgroundColor = '#f0f9ff';
735
+ setTimeout(() => {
736
+ paramsDisplay.style.backgroundColor = originalColor;
737
+ }, 500);
738
+ } else {
739
+ // Regular update for other node types
740
+ paramsDisplay.textContent = `Params: ${formatNumber(parameters)}`;
741
+ }
742
+ console.log('Updated parameters display');
743
+ }
744
+ }
745
+
746
+ if (paramsDetailsDisplay) {
747
+ paramsDetailsDisplay.innerHTML = paramsDetails;
748
+ console.log('Updated parameter details display');
749
+ }
750
+
751
+ if (dimensionsDisplay && outputShape) {
752
+ let dimensionsText = '';
753
+ if (nodeType === 'hidden' || nodeType === 'output' || nodeType === 'linear') {
754
+ dimensionsText = config.units || '';
755
+ } else if (nodeType === 'conv' || nodeType === 'pool') {
756
+ if (Array.isArray(outputShape)) {
757
+ dimensionsText = outputShape.join('×');
758
+ } else {
759
+ dimensionsText = outputShape;
760
+ }
761
+ } else if (nodeType === 'input') {
762
+ if (Array.isArray(config.shape)) {
763
+ dimensionsText = config.shape.join('×');
764
+ } else {
765
+ dimensionsText = config.shape || '';
766
+ }
767
+ }
768
+ dimensionsDisplay.textContent = dimensionsText;
769
+ console.log('Updated dimensions display');
770
+ }
771
+
772
+ // Update the model to ensure propagation of changes
773
+ if (window.dragDrop) {
774
+ if (window.dragDrop.getNetworkArchitecture) {
775
+ const networkLayers = window.dragDrop.getNetworkArchitecture();
776
+ const layerIndex = networkLayers.layers.findIndex(layer => layer.id === nodeId);
777
+
778
+ if (layerIndex !== -1) {
779
+ networkLayers.layers[layerIndex].config = { ...config };
780
+ if (parameters !== undefined) {
781
+ networkLayers.layers[layerIndex].parameters = parameters;
782
+ }
783
+
784
+ // Update connections to propagate parameter changes to connected nodes
785
+ if (window.dragDrop.updateConnections) {
786
+ window.dragDrop.updateConnections();
787
+ }
788
+
789
+ // Update downstream nodes to propagate parameter changes through the network
790
+ if (window.dragDrop.forceUpdateNetworkParameters) {
791
+ console.log('Forcing network parameter update');
792
+
793
+ // Add a small delay to ensure the current node update is complete
794
+ setTimeout(() => {
795
+ window.dragDrop.forceUpdateNetworkParameters();
796
+
797
+ // Another update after a short delay for deeper propagation
798
+ setTimeout(() => {
799
+ window.dragDrop.updateConnections();
800
+ console.log('Final connection update completed');
801
+ }, 100);
802
+ }, 50);
803
+ }
804
+
805
+ // Notify about the network update
806
+ document.dispatchEvent(new CustomEvent('networkUpdated', {
807
+ detail: networkLayers
808
+ }));
809
+ console.log('Dispatched networkUpdated event with updated model');
810
+ } else {
811
+ console.warn(`Node ${nodeId} not found in network model layers`);
812
+ }
813
+ }
814
+
815
+ // Force re-rendering of all connections
816
+ if (window.dragDrop.updateConnections) {
817
+ setTimeout(() => {
818
+ window.dragDrop.updateConnections();
819
+ console.log('Updated all connections after parameter change');
820
+ }, 50);
821
+ }
822
+ }
823
+
824
+ console.log(`Completed update of node ${nodeId} with config:`, config);
825
+ }
826
+
827
+ /**
828
+ * Format large numbers for display
829
+ */
830
+ function formatNumber(num) {
831
+ // Safety check for invalid values
832
+ if (num === null || num === undefined) return 'N/A';
833
+ if (num === 0) return '0';
834
+
835
+ // Try to convert strings to numbers
836
+ if (typeof num === 'string') {
837
+ if (num === '?' || num.toLowerCase() === 'n/a') return 'N/A';
838
+ num = parseFloat(num);
839
+ }
840
+
841
+ // Handle NaN
842
+ if (isNaN(num)) return 'N/A';
843
+
844
+ // Format based on size
845
+ if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
846
+ if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
847
+ if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
848
+
849
+ // Handle smaller numbers with decimal places
850
+ if (num < 1e3 && num % 1 !== 0) {
851
+ return num.toFixed(2);
852
+ }
853
+
854
+ return num.toString();
855
+ }
856
+
857
+ /**
858
+ * Helper function to manually recalculate Conv2D parameters
859
+ * This can be called from the console for debugging
860
+ */
861
+ function forceRecalculateConv2DParameters(nodeId) {
862
+ // If no ID provided, try to find all Conv2D nodes
863
+ if (!nodeId) {
864
+ const conv2dNodes = document.querySelectorAll('.canvas-node[data-type="conv"]');
865
+ if (conv2dNodes.length === 0) {
866
+ console.log('No Conv2D nodes found to update');
867
+ return;
868
+ }
869
+
870
+ console.log(`Found ${conv2dNodes.length} Conv2D nodes to update`);
871
+
872
+ // Update each Conv2D node
873
+ conv2dNodes.forEach(node => {
874
+ const id = node.getAttribute('data-id');
875
+ console.log(`Updating Conv2D node ${id}`);
876
+ forceRecalculateConv2DParameters(id);
877
+ });
878
+ return;
879
+ }
880
+
881
+ // Find the specific node
882
+ const node = document.querySelector(`.canvas-node[data-id="${nodeId}"]`);
883
+ if (!node) {
884
+ console.error(`Node with ID ${nodeId} not found`);
885
+ return;
886
+ }
887
+
888
+ // Check if it's a Conv2D node
889
+ const nodeType = node.getAttribute('data-type');
890
+ if (nodeType !== 'conv') {
891
+ console.error(`Node ${nodeId} is not a Conv2D node (type: ${nodeType})`);
892
+ return;
893
+ }
894
+
895
+ // Get the current config
896
+ const config = node.layerConfig || {};
897
+
898
+ // Force the update
899
+ console.log(`Forcing parameter recalculation for Conv2D node ${nodeId}`);
900
+ updateNodeWithConfig(node, 'conv', config);
901
+
902
+ // If dragDrop is available, force a network update
903
+ if (window.dragDrop && window.dragDrop.forceUpdateNetworkParameters) {
904
+ setTimeout(() => {
905
+ window.dragDrop.forceUpdateNetworkParameters();
906
+ }, 100);
907
+ }
908
+ }
909
+
910
+ // Expose helper function to window for debugging
911
+ window.forceRecalculateConv2DParameters = forceRecalculateConv2DParameters;
912
+ })();
js/main.js CHANGED
@@ -35,6 +35,66 @@ document.addEventListener('DOMContentLoaded', () => {
35
  // Listen for layer editor events
36
  document.addEventListener('openLayerEditor', handleOpenLayerEditor);
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  // Setup UI controls and event listeners
39
  function setupUIControls() {
40
  // Learning rate slider
@@ -138,7 +198,22 @@ document.addEventListener('DOMContentLoaded', () => {
138
  // Save button
139
  const saveButton = layerEditorModal.querySelector('.save-layer-btn');
140
  if (saveButton) {
141
- saveButton.addEventListener('click', saveLayerConfig);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  }
143
  }
144
  }
@@ -300,8 +375,14 @@ document.addEventListener('DOMContentLoaded', () => {
300
  // Handle opening the layer editor
301
  function handleOpenLayerEditor(e) {
302
  const node = e.detail.node;
303
- const nodeType = node.getAttribute('data-type');
304
- const layerId = node.getAttribute('data-id');
 
 
 
 
 
 
305
 
306
  // Get current configuration
307
  const layerConfig = node.layerConfig || window.neuralNetwork.createNodeConfig(nodeType);
@@ -319,6 +400,9 @@ document.addEventListener('DOMContentLoaded', () => {
319
  // Clear previous form fields
320
  layerForm.innerHTML = '';
321
 
 
 
 
322
  // Create form fields based on layer type
323
  switch (nodeType) {
324
  case 'input':
@@ -698,34 +782,36 @@ document.addEventListener('DOMContentLoaded', () => {
698
  }
699
  }
700
 
701
- // Add save and cancel buttons
702
- layerForm.innerHTML += `
703
- <div class="form-buttons">
704
- <button type="button" id="save-layer-config" class="btn-primary">Save Changes</button>
705
- <button type="button" id="cancel-layer-edit" class="btn-secondary">Cancel</button>
706
- </div>
707
- `;
708
-
709
  // Open the modal
710
  const modal = document.getElementById('layer-editor-modal');
711
  if (modal) {
712
  openModal(modal);
713
 
714
- // Add event listeners for buttons
715
- const saveButton = document.getElementById('save-layer-config');
716
  if (saveButton) {
717
- saveButton.addEventListener('click', () => {
 
 
 
 
 
718
  saveLayerConfig(node, nodeType, layerId);
719
  closeModal(modal);
720
  });
721
  }
722
 
723
- const cancelButton = document.getElementById('cancel-layer-edit');
724
- if (cancelButton) {
725
- cancelButton.addEventListener('click', () => {
 
 
 
 
 
726
  closeModal(modal);
727
  });
728
- }
729
  }
730
  }
731
 
@@ -976,12 +1062,50 @@ document.addEventListener('DOMContentLoaded', () => {
976
  networkLayers.layers[layerIndex].parameters = layerConfig.parameters;
977
  }
978
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
979
  // Trigger network updated event
980
  const event = new CustomEvent('networkUpdated', { detail: networkLayers });
981
  document.dispatchEvent(event);
982
 
983
- // Update connected nodes to propagate shape changes
984
- updateNodeConnections(node, layerId);
985
  }
986
 
987
  // Helper function to update connections between nodes when shapes change
 
35
  // Listen for layer editor events
36
  document.addEventListener('openLayerEditor', handleOpenLayerEditor);
37
 
38
+ // Tab switching functionality
39
+ const tabButtons = document.querySelectorAll('.tab-button');
40
+ const tabContents = document.querySelectorAll('.tab-content');
41
+
42
+ tabButtons.forEach(button => {
43
+ button.addEventListener('click', () => {
44
+ // Get the tab data attribute
45
+ const tabId = button.getAttribute('data-tab');
46
+
47
+ // Remove active class from all buttons and contents
48
+ tabButtons.forEach(btn => btn.classList.remove('active'));
49
+ tabContents.forEach(content => content.classList.remove('active'));
50
+
51
+ // Add active class to the clicked button
52
+ button.classList.add('active');
53
+
54
+ // Add active class to the corresponding content
55
+ const tabContent = document.getElementById(`${tabId}-tab`);
56
+ if (tabContent) {
57
+ tabContent.classList.add('active');
58
+
59
+ // Dispatch a custom event to notify tab-specific scripts
60
+ document.dispatchEvent(new CustomEvent('tabSwitch', {
61
+ detail: { tab: tabId }
62
+ }));
63
+ }
64
+ });
65
+ });
66
+
67
+ // Modal functionality
68
+ const aboutLink = document.getElementById('about-link');
69
+ const guideLink = document.getElementById('guide-link');
70
+ const aboutModal = document.getElementById('about-modal');
71
+ const closeModalButtons = document.querySelectorAll('.close-modal');
72
+
73
+ if (aboutLink && aboutModal) {
74
+ aboutLink.addEventListener('click', (e) => {
75
+ e.preventDefault();
76
+ aboutModal.style.display = 'flex';
77
+ });
78
+ }
79
+
80
+ if (closeModalButtons) {
81
+ closeModalButtons.forEach(button => {
82
+ button.addEventListener('click', () => {
83
+ const modal = button.closest('.modal');
84
+ if (modal) {
85
+ modal.style.display = 'none';
86
+ }
87
+ });
88
+ });
89
+ }
90
+
91
+ // Close modals when clicking outside content
92
+ window.addEventListener('click', (e) => {
93
+ if (e.target.classList.contains('modal')) {
94
+ e.target.style.display = 'none';
95
+ }
96
+ });
97
+
98
  // Setup UI controls and event listeners
99
  function setupUIControls() {
100
  // Learning rate slider
 
198
  // Save button
199
  const saveButton = layerEditorModal.querySelector('.save-layer-btn');
200
  if (saveButton) {
201
+ saveButton.addEventListener('click', () => {
202
+ // Get node reference from modal data attributes
203
+ const nodeRef = layerEditorModal.getAttribute('data-node-reference');
204
+ const nodeType = layerEditorModal.getAttribute('data-node-type');
205
+ const nodeId = layerEditorModal.getAttribute('data-node-id');
206
+
207
+ // Get actual DOM node using the ID
208
+ const node = document.querySelector(`.canvas-node[data-id="${nodeId}"]`);
209
+
210
+ if (node) {
211
+ saveLayerConfig(node, nodeType, nodeId);
212
+ }
213
+
214
+ // Close the modal after saving
215
+ closeModal(layerEditorModal);
216
+ });
217
  }
218
  }
219
  }
 
375
  // Handle opening the layer editor
376
  function handleOpenLayerEditor(e) {
377
  const node = e.detail.node;
378
+ const nodeType = e.detail.type;
379
+ const layerId = e.detail.id;
380
+
381
+ // Store information in the modal for later use
382
+ const layerEditorModal = document.getElementById('layer-editor-modal');
383
+ layerEditorModal.setAttribute('data-node-reference', layerId);
384
+ layerEditorModal.setAttribute('data-node-type', nodeType);
385
+ layerEditorModal.setAttribute('data-node-id', layerId);
386
 
387
  // Get current configuration
388
  const layerConfig = node.layerConfig || window.neuralNetwork.createNodeConfig(nodeType);
 
400
  // Clear previous form fields
401
  layerForm.innerHTML = '';
402
 
403
+ // Show modal
404
+ openModal(layerEditorModal);
405
+
406
  // Create form fields based on layer type
407
  switch (nodeType) {
408
  case 'input':
 
782
  }
783
  }
784
 
 
 
 
 
 
 
 
 
785
  // Open the modal
786
  const modal = document.getElementById('layer-editor-modal');
787
  if (modal) {
788
  openModal(modal);
789
 
790
+ // Add event listeners for the buttons in the modal footer
791
+ const saveButton = modal.querySelector('.modal-footer .save-layer-btn');
792
  if (saveButton) {
793
+ // Remove any existing event listeners
794
+ const newSaveButton = saveButton.cloneNode(true);
795
+ saveButton.parentNode.replaceChild(newSaveButton, saveButton);
796
+
797
+ // Add new event listener
798
+ newSaveButton.addEventListener('click', () => {
799
  saveLayerConfig(node, nodeType, layerId);
800
  closeModal(modal);
801
  });
802
  }
803
 
804
+ const cancelButtons = modal.querySelectorAll('.modal-footer .close-modal');
805
+ cancelButtons.forEach(cancelButton => {
806
+ // Remove any existing event listeners
807
+ const newCancelButton = cancelButton.cloneNode(true);
808
+ cancelButton.parentNode.replaceChild(newCancelButton, cancelButton);
809
+
810
+ // Add new event listener
811
+ newCancelButton.addEventListener('click', () => {
812
  closeModal(modal);
813
  });
814
+ });
815
  }
816
  }
817
 
 
1062
  networkLayers.layers[layerIndex].parameters = layerConfig.parameters;
1063
  }
1064
 
1065
+ // Find all connections from this node and update target nodes
1066
+ const connections = document.querySelectorAll(`.connection[data-source="${layerId}"]`);
1067
+ connections.forEach(connection => {
1068
+ const targetId = connection.getAttribute('data-target');
1069
+ const targetNode = document.querySelector(`.canvas-node[data-id="${targetId}"]`);
1070
+
1071
+ if (targetNode && targetNode.layerConfig) {
1072
+ // Update target node's input shape based on this node's output shape
1073
+ if (layerConfig.outputShape) {
1074
+ targetNode.layerConfig.inputShape = layerConfig.outputShape;
1075
+
1076
+ // Recalculate parameters
1077
+ const targetType = targetNode.getAttribute('data-type');
1078
+ const newParams = window.neuralNetwork.calculateParameters(
1079
+ targetType,
1080
+ targetNode.layerConfig,
1081
+ layerConfig
1082
+ );
1083
+
1084
+ if (newParams) {
1085
+ targetNode.layerConfig.parameters = newParams;
1086
+
1087
+ // Update parameter display
1088
+ const paramsDisplay = targetNode.querySelector('.node-parameters');
1089
+ if (paramsDisplay) {
1090
+ paramsDisplay.textContent = `Params: ${formatNumber(newParams)}`;
1091
+ }
1092
+
1093
+ // Update input shape display
1094
+ const inputShapeDisplay = targetNode.querySelector('.input-shape');
1095
+ if (inputShapeDisplay) {
1096
+ inputShapeDisplay.textContent = `[${targetNode.layerConfig.inputShape.join(' × ')}]`;
1097
+ }
1098
+ }
1099
+ }
1100
+ }
1101
+ });
1102
+
1103
  // Trigger network updated event
1104
  const event = new CustomEvent('networkUpdated', { detail: networkLayers });
1105
  document.dispatchEvent(event);
1106
 
1107
+ // Update all connections to reflect the new shapes and positions
1108
+ window.dragDrop.updateConnections();
1109
  }
1110
 
1111
  // Helper function to update connections between nodes when shapes change
js/tab-manager.js ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Tab Manager - Handles tab switching and ensures animations are properly initialized
2
+ document.addEventListener('DOMContentLoaded', () => {
3
+ console.log('Tab Manager Initialized');
4
+
5
+ const tabButtons = document.querySelectorAll('.tab-button');
6
+ const tabContents = document.querySelectorAll('.tab-content');
7
+
8
+ // Current active tab
9
+ let currentTab = 'network-design'; // Default tab
10
+
11
+ // Function to activate a tab
12
+ function activateTab(tabId) {
13
+ console.log(`Activating tab: ${tabId}`);
14
+ currentTab = tabId;
15
+
16
+ // Remove active class from all buttons and contents
17
+ tabButtons.forEach(btn => btn.classList.remove('active'));
18
+ tabContents.forEach(content => content.classList.remove('active'));
19
+
20
+ // Add active class to the requested tab button
21
+ const button = document.querySelector(`.tab-button[data-tab="${tabId}"]`);
22
+ if (button) {
23
+ button.classList.add('active');
24
+ }
25
+
26
+ // Add active class to the corresponding content
27
+ const tabContent = document.getElementById(`${tabId}-tab`);
28
+ if (tabContent) {
29
+ tabContent.classList.add('active');
30
+ }
31
+
32
+ // Dispatch a custom event to notify tab-specific scripts
33
+ document.dispatchEvent(new CustomEvent('tabSwitch', {
34
+ detail: { tab: tabId }
35
+ }));
36
+
37
+ // Initialize canvas for the appropriate tab
38
+ initializeTabContent(tabId);
39
+ }
40
+
41
+ // Function to initialize tab content, especially canvases
42
+ function initializeTabContent(tabId) {
43
+ let canvas, ctx;
44
+
45
+ switch(tabId) {
46
+ case 'backpropagation':
47
+ canvas = document.getElementById('backprop-canvas');
48
+ if (canvas && typeof window.initBackpropCanvas === 'function') {
49
+ console.log('Initializing backpropagation canvas');
50
+ window.initBackpropCanvas();
51
+ } else {
52
+ console.warn('Could not initialize backpropagation canvas');
53
+ }
54
+ break;
55
+
56
+ case 'forward-propagation':
57
+ canvas = document.getElementById('forward-canvas');
58
+ if (canvas && typeof window.initForwardPropCanvas === 'function') {
59
+ console.log('Initializing forward propagation canvas');
60
+ window.initForwardPropCanvas();
61
+ } else {
62
+ console.warn('Could not initialize forward propagation canvas');
63
+
64
+ // Fallback - directly draw on canvas if found
65
+ if (canvas) {
66
+ ctx = canvas.getContext('2d');
67
+ if (ctx) {
68
+ // Set canvas size to match container
69
+ const container = canvas.parentElement;
70
+ if (container) {
71
+ canvas.width = container.clientWidth;
72
+ canvas.height = container.clientHeight;
73
+ } else {
74
+ canvas.width = 800;
75
+ canvas.height = 400;
76
+ }
77
+
78
+ // Draw a placeholder network
79
+ drawPlaceholderNetwork(ctx, canvas.width, canvas.height);
80
+ }
81
+ }
82
+ }
83
+ break;
84
+
85
+ case 'background-animation':
86
+ canvas = document.getElementById('background-canvas');
87
+ if (canvas && typeof window.initBackgroundCanvas === 'function') {
88
+ console.log('Initializing background animation canvas');
89
+ window.initBackgroundCanvas();
90
+ } else {
91
+ console.warn('Could not initialize background animation canvas');
92
+
93
+ // Fallback - directly draw on canvas if found
94
+ if (canvas) {
95
+ ctx = canvas.getContext('2d');
96
+ if (ctx) {
97
+ // Set canvas size to match container
98
+ const container = canvas.parentElement;
99
+ if (container) {
100
+ canvas.width = container.clientWidth;
101
+ canvas.height = container.clientHeight;
102
+ } else {
103
+ canvas.width = 800;
104
+ canvas.height = 400;
105
+ }
106
+
107
+ // Draw a placeholder animation
108
+ drawPlaceholderNeurons(ctx, canvas.width, canvas.height);
109
+ }
110
+ }
111
+ }
112
+ break;
113
+ }
114
+ }
115
+
116
+ // Helper function to draw a placeholder neural network
117
+ function drawPlaceholderNetwork(ctx, width, height) {
118
+ // Clear canvas
119
+ ctx.clearRect(0, 0, width, height);
120
+
121
+ // Draw background
122
+ ctx.fillStyle = '#f8f9fa';
123
+ ctx.fillRect(0, 0, width, height);
124
+
125
+ // Define network layout
126
+ const layers = [3, 4, 2]; // Input, hidden, output layers
127
+ const neuronRadius = 20;
128
+ const layerSpacing = width / (layers.length + 1);
129
+
130
+ // Function to calculate neuron positions
131
+ function getNeuronPosition(layerIndex, neuronIndex, totalNeurons) {
132
+ const x = layerSpacing * (layerIndex + 1);
133
+ const layerHeight = totalNeurons * (neuronRadius * 2 + 10);
134
+ const startY = (height - layerHeight) / 2 + neuronRadius;
135
+ const y = startY + neuronIndex * (neuronRadius * 2 + 10);
136
+ return { x, y };
137
+ }
138
+
139
+ // Draw connections first (so they appear behind neurons)
140
+ ctx.strokeStyle = '#aaa';
141
+ ctx.lineWidth = 1;
142
+
143
+ // For each layer except the last
144
+ for (let layerIndex = 0; layerIndex < layers.length - 1; layerIndex++) {
145
+ const sourceLayer = layers[layerIndex];
146
+ const targetLayer = layers[layerIndex + 1];
147
+
148
+ // Connect each neuron in source layer to each neuron in target layer
149
+ for (let sourceNeuron = 0; sourceNeuron < sourceLayer; sourceNeuron++) {
150
+ const source = getNeuronPosition(layerIndex, sourceNeuron, sourceLayer);
151
+
152
+ for (let targetNeuron = 0; targetNeuron < targetLayer; targetNeuron++) {
153
+ const target = getNeuronPosition(layerIndex + 1, targetNeuron, targetLayer);
154
+
155
+ // Draw connection
156
+ ctx.beginPath();
157
+ ctx.moveTo(source.x, source.y);
158
+ ctx.lineTo(target.x, target.y);
159
+ ctx.stroke();
160
+ }
161
+ }
162
+ }
163
+
164
+ // Draw neurons
165
+ const layerColors = ['#6495ED', '#7B68EE', '#9370DB']; // Different color for each layer
166
+
167
+ for (let layerIndex = 0; layerIndex < layers.length; layerIndex++) {
168
+ const neuronsInLayer = layers[layerIndex];
169
+
170
+ for (let neuronIndex = 0; neuronIndex < neuronsInLayer; neuronIndex++) {
171
+ const { x, y } = getNeuronPosition(layerIndex, neuronIndex, neuronsInLayer);
172
+
173
+ // Draw neuron circle
174
+ ctx.beginPath();
175
+ ctx.arc(x, y, neuronRadius, 0, Math.PI * 2);
176
+ ctx.fillStyle = layerColors[layerIndex];
177
+ ctx.fill();
178
+ ctx.strokeStyle = '#fff';
179
+ ctx.lineWidth = 2;
180
+ ctx.stroke();
181
+ }
182
+ }
183
+
184
+ // Add text to explain placeholder
185
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
186
+ ctx.font = '18px Arial';
187
+ ctx.textAlign = 'center';
188
+ ctx.fillText('Animation Placeholder - Check Console for Errors', width/2, height - 30);
189
+ }
190
+
191
+ // Helper function to draw placeholder neurons for background animation
192
+ function drawPlaceholderNeurons(ctx, width, height) {
193
+ // Clear canvas
194
+ ctx.clearRect(0, 0, width, height);
195
+
196
+ // Draw background
197
+ ctx.fillStyle = '#f8f9fa';
198
+ ctx.fillRect(0, 0, width, height);
199
+
200
+ // Create random neurons
201
+ const neurons = [];
202
+ const neuronCount = 50;
203
+
204
+ for (let i = 0; i < neuronCount; i++) {
205
+ neurons.push({
206
+ x: Math.random() * width,
207
+ y: Math.random() * height,
208
+ radius: 3 + Math.random() * 5,
209
+ color: Math.random() > 0.8 ? '#6495ED' : '#aaaaaa'
210
+ });
211
+ }
212
+
213
+ // Draw connections
214
+ ctx.strokeStyle = 'rgba(170, 170, 170, 0.3)';
215
+ ctx.lineWidth = 1;
216
+
217
+ for (let i = 0; i < neurons.length; i++) {
218
+ const source = neurons[i];
219
+
220
+ // Connect to nearby neurons
221
+ for (let j = i + 1; j < neurons.length; j++) {
222
+ const target = neurons[j];
223
+ const distance = Math.sqrt(
224
+ Math.pow(target.x - source.x, 2) +
225
+ Math.pow(target.y - source.y, 2)
226
+ );
227
+
228
+ // Only connect neurons that are close enough
229
+ if (distance < 100) {
230
+ ctx.beginPath();
231
+ ctx.moveTo(source.x, source.y);
232
+ ctx.lineTo(target.x, target.y);
233
+ ctx.stroke();
234
+ }
235
+ }
236
+ }
237
+
238
+ // Draw neurons
239
+ neurons.forEach(neuron => {
240
+ ctx.beginPath();
241
+ ctx.arc(neuron.x, neuron.y, neuron.radius, 0, Math.PI * 2);
242
+ ctx.fillStyle = neuron.color;
243
+ ctx.fill();
244
+ });
245
+
246
+ // Add text to explain placeholder
247
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
248
+ ctx.font = '18px Arial';
249
+ ctx.textAlign = 'center';
250
+ ctx.fillText('Animation Placeholder - Check Console for Errors', width/2, height - 30);
251
+ }
252
+
253
+ // Add event listeners to tab buttons
254
+ tabButtons.forEach(button => {
255
+ button.addEventListener('click', () => {
256
+ const tabId = button.getAttribute('data-tab');
257
+ activateTab(tabId);
258
+ });
259
+ });
260
+
261
+ // Export the activateTab function to window for access from other scripts
262
+ window.activateTab = activateTab;
263
+
264
+ // Register initialization functions that each animation script should call
265
+ window.initBackpropCanvas = null;
266
+ window.initForwardPropCanvas = null;
267
+ window.initBackgroundCanvas = null;
268
+
269
+ // Monitor tab visibility for better animation performance
270
+ document.addEventListener('visibilitychange', () => {
271
+ if (document.visibilityState === 'visible') {
272
+ console.log('Page is now visible, refreshing current tab:', currentTab);
273
+ // Re-initialize the current tab when the page becomes visible again
274
+ initializeTabContent(currentTab);
275
+ }
276
+ });
277
+
278
+ // Check window resizing for canvas sizing
279
+ window.addEventListener('resize', () => {
280
+ console.log('Window resized, refreshing current tab:', currentTab);
281
+ initializeTabContent(currentTab);
282
+ });
283
+ });