snapo commited on
Commit
5c8e55b
·
verified ·
1 Parent(s): ea5229c

looks greate, but when i connect 1 node to another gray temporary lines popup, this looks great but they are offset a lot to when the connection happens. this is a bug - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +963 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Nodesworkflow
3
- emoji: 🐨
4
- colorFrom: red
5
- colorTo: blue
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: nodesworkflow
3
+ emoji: 🐳
4
+ colorFrom: yellow
5
+ colorTo: pink
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,963 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Lumiflow - Visual Workflow Builder</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .connection {
11
+ pointer-events: none;
12
+ }
13
+ .connection-delete-btn {
14
+ pointer-events: all;
15
+ }
16
+ .node {
17
+ transition: transform 0.1s ease;
18
+ }
19
+ .node:hover {
20
+ transform: scale(1.02);
21
+ }
22
+ .node-handle {
23
+ width: 12px;
24
+ height: 12px;
25
+ border-radius: 50%;
26
+ cursor: crosshair;
27
+ }
28
+ .node-handle.input {
29
+ left: -6px;
30
+ }
31
+ .node-handle.output {
32
+ right: -6px;
33
+ }
34
+ .connection-line {
35
+ stroke-width: 3;
36
+ fill: none;
37
+ }
38
+ .connection-line.temp {
39
+ stroke-dasharray: 5;
40
+ }
41
+ .flow-canvas {
42
+ background-image:
43
+ linear-gradient(#e5e7eb 1px, transparent 1px),
44
+ linear-gradient(90deg, #e5e7eb 1px, transparent 1px);
45
+ background-size: 20px 20px;
46
+ }
47
+ .dark .flow-canvas {
48
+ background-image:
49
+ linear-gradient(#374151 1px, transparent 1px),
50
+ linear-gradient(90deg, #374151 1px, transparent 1px);
51
+ }
52
+ .node-config-panel {
53
+ transition: all 0.3s ease;
54
+ }
55
+ .node-config-panel.hidden {
56
+ transform: translateX(100%);
57
+ opacity: 0;
58
+ }
59
+ .node-config-panel.visible {
60
+ transform: translateX(0);
61
+ opacity: 1;
62
+ }
63
+ </style>
64
+ </head>
65
+ <body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 transition-colors duration-300">
66
+ <div class="flex flex-col h-screen">
67
+ <!-- Header -->
68
+ <header class="bg-blue-600 dark:bg-blue-800 text-white p-4 shadow-md">
69
+ <div class="container mx-auto flex justify-between items-center">
70
+ <div class="flex items-center space-x-2">
71
+ <i class="fas fa-project-diagram text-2xl"></i>
72
+ <h1 class="text-2xl font-bold">Lumiflow</h1>
73
+ </div>
74
+ <div class="flex items-center space-x-4">
75
+ <button id="run-flow-btn" class="bg-green-500 hover:bg-green-600 px-4 py-2 rounded-md font-medium flex items-center space-x-2">
76
+ <i class="fas fa-play"></i>
77
+ <span>Run Flow</span>
78
+ </button>
79
+ <button id="theme-toggle" class="p-2 rounded-full hover:bg-blue-700 dark:hover:bg-blue-900">
80
+ <i class="fas fa-moon dark:hidden"></i>
81
+ <i class="fas fa-sun hidden dark:block"></i>
82
+ </button>
83
+ </div>
84
+ </div>
85
+ </header>
86
+
87
+ <!-- Main Content -->
88
+ <div class="flex flex-1 overflow-hidden">
89
+ <!-- Node Panel -->
90
+ <div class="w-64 bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 p-4 overflow-y-auto">
91
+ <h2 class="font-bold text-lg mb-4">Nodes</h2>
92
+
93
+ <div class="mb-6">
94
+ <h3 class="font-semibold text-sm uppercase text-gray-500 dark:text-gray-400 mb-2 flex items-center">
95
+ <i class="fas fa-sign-in-alt mr-2"></i>
96
+ Input
97
+ </h3>
98
+ <div class="space-y-2">
99
+ <div class="node-item bg-blue-50 dark:bg-blue-900/30 p-3 rounded-md cursor-move hover:bg-blue-100 dark:hover:bg-blue-900/50" draggable="true" data-type="inject">
100
+ <div class="flex items-center">
101
+ <i class="fas fa-bolt text-blue-500 mr-2"></i>
102
+ <span>Inject</span>
103
+ </div>
104
+ </div>
105
+ <div class="node-item bg-blue-50 dark:bg-blue-900/30 p-3 rounded-md cursor-move hover:bg-blue-100 dark:hover:bg-blue-900/50" draggable="true" data-type="http-in">
106
+ <div class="flex items-center">
107
+ <i class="fas fa-globe text-blue-500 mr-2"></i>
108
+ <span>HTTP In</span>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="mb-6">
115
+ <h3 class="font-semibold text-sm uppercase text-gray-500 dark:text-gray-400 mb-2 flex items-center">
116
+ <i class="fas fa-cogs mr-2"></i>
117
+ Processing
118
+ </h3>
119
+ <div class="space-y-2">
120
+ <div class="node-item bg-purple-50 dark:bg-purple-900/30 p-3 rounded-md cursor-move hover:bg-purple-100 dark:hover:bg-purple-900/50" draggable="true" data-type="function">
121
+ <div class="flex items-center">
122
+ <i class="fas fa-code text-purple-500 mr-2"></i>
123
+ <span>Function</span>
124
+ </div>
125
+ </div>
126
+ <div class="node-item bg-purple-50 dark:bg-purple-900/30 p-3 rounded-md cursor-move hover:bg-purple-100 dark:hover:bg-purple-900/50" draggable="true" data-type="delay">
127
+ <div class="flex items-center">
128
+ <i class="fas fa-clock text-purple-500 mr-2"></i>
129
+ <span>Delay</span>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <div class="mb-6">
136
+ <h3 class="font-semibold text-sm uppercase text-gray-500 dark:text-gray-400 mb-2 flex items-center">
137
+ <i class="fas fa-sign-out-alt mr-2"></i>
138
+ Output
139
+ </h3>
140
+ <div class="space-y-2">
141
+ <div class="node-item bg-green-50 dark:bg-green-900/30 p-3 rounded-md cursor-move hover:bg-green-100 dark:hover:bg-green-900/50" draggable="true" data-type="debug">
142
+ <div class="flex items-center">
143
+ <i class="fas fa-bug text-green-500 mr-2"></i>
144
+ <span>Debug</span>
145
+ </div>
146
+ </div>
147
+ <div class="node-item bg-green-50 dark:bg-green-900/30 p-3 rounded-md cursor-move hover:bg-green-100 dark:hover:bg-green-900/50" draggable="true" data-type="http-out">
148
+ <div class="flex items-center">
149
+ <i class="fas fa-server text-green-500 mr-2"></i>
150
+ <span>HTTP Out</span>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+ <!-- Flow Canvas -->
158
+ <div class="flex-1 relative overflow-hidden">
159
+ <div id="flow-canvas" class="flow-canvas w-full h-full bg-white dark:bg-gray-900 relative overflow-auto">
160
+ <svg id="connections-svg" class="absolute top-0 left-0 w-full h-full pointer-events-none z-10"></svg>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Configuration Panel -->
165
+ <div id="config-panel" class="node-config-panel hidden w-80 bg-white dark:bg-gray-800 border-l border-gray-200 dark:border-gray-700 p-4 overflow-y-auto">
166
+ <div class="flex justify-between items-center mb-4">
167
+ <h2 class="font-bold text-lg">Node Configuration</h2>
168
+ <button id="close-config-btn" class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700">
169
+ <i class="fas fa-times"></i>
170
+ </button>
171
+ </div>
172
+
173
+ <div id="node-config-content">
174
+ <div class="text-center py-10 text-gray-500">
175
+ <i class="fas fa-cog text-4xl mb-2"></i>
176
+ <p>Select a node to configure</p>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ <!-- Status Bar -->
183
+ <footer class="bg-gray-200 dark:bg-gray-800 border-t border-gray-300 dark:border-gray-700 p-2 text-sm">
184
+ <div class="container mx-auto flex justify-between items-center">
185
+ <div>
186
+ <span id="node-count">0 nodes</span>
187
+ <span class="mx-2">•</span>
188
+ <span id="connection-count">0 connections</span>
189
+ </div>
190
+ <div id="status-message" class="font-medium">Ready</div>
191
+ </div>
192
+ </footer>
193
+ </div>
194
+
195
+ <script>
196
+ document.addEventListener('DOMContentLoaded', function() {
197
+ // Theme toggle
198
+ const themeToggle = document.getElementById('theme-toggle');
199
+ const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
200
+ const currentTheme = localStorage.getItem('theme') || (prefersDarkScheme.matches ? 'dark' : 'light');
201
+
202
+ if (currentTheme === 'dark') {
203
+ document.body.classList.add('dark');
204
+ }
205
+
206
+ themeToggle.addEventListener('click', function() {
207
+ document.body.classList.toggle('dark');
208
+ const theme = document.body.classList.contains('dark') ? 'dark' : 'light';
209
+ localStorage.setItem('theme', theme);
210
+ });
211
+
212
+ // Flow state
213
+ const state = {
214
+ nodes: [],
215
+ connections: [],
216
+ selectedNode: null,
217
+ draggingNode: null,
218
+ creatingConnection: null,
219
+ tempConnection: null,
220
+ offset: { x: 0, y: 0 },
221
+ nextNodeId: 1,
222
+ nextConnectionId: 1
223
+ };
224
+
225
+ // DOM elements
226
+ const flowCanvas = document.getElementById('flow-canvas');
227
+ const connectionsSvg = document.getElementById('connections-svg');
228
+ const configPanel = document.getElementById('config-panel');
229
+ const nodeConfigContent = document.getElementById('node-config-content');
230
+ const nodeCountEl = document.getElementById('node-count');
231
+ const connectionCountEl = document.getElementById('connection-count');
232
+ const statusMessageEl = document.getElementById('status-message');
233
+ const runFlowBtn = document.getElementById('run-flow-btn');
234
+ const closeConfigBtn = document.getElementById('close-config-btn');
235
+
236
+ // Node templates
237
+ const nodeTemplates = {
238
+ 'inject': {
239
+ name: 'Inject',
240
+ icon: 'bolt',
241
+ color: 'blue',
242
+ config: `
243
+ <div class="space-y-4">
244
+ <div>
245
+ <label class="block text-sm font-medium mb-1">Name</label>
246
+ <input type="text" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
247
+ </div>
248
+ <div>
249
+ <label class="block text-sm font-medium mb-1">Payload</label>
250
+ <select class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
251
+ <option>Timestamp</option>
252
+ <option>String</option>
253
+ <option>Number</option>
254
+ <option>Boolean</option>
255
+ <option>JSON</option>
256
+ </select>
257
+ </div>
258
+ <div>
259
+ <label class="flex items-center">
260
+ <input type="checkbox" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400">
261
+ <span class="ml-2 text-sm">Repeat</span>
262
+ </label>
263
+ </div>
264
+ <div class="repeat-options hidden">
265
+ <label class="block text-sm font-medium mb-1">Interval (ms)</label>
266
+ <input type="number" value="1000" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
267
+ </div>
268
+ </div>
269
+ `
270
+ },
271
+ 'http-in': {
272
+ name: 'HTTP In',
273
+ icon: 'globe',
274
+ color: 'blue',
275
+ config: `
276
+ <div class="space-y-4">
277
+ <div>
278
+ <label class="block text-sm font-medium mb-1">Method</label>
279
+ <select class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
280
+ <option>GET</option>
281
+ <option>POST</option>
282
+ <option>PUT</option>
283
+ <option>DELETE</option>
284
+ </select>
285
+ </div>
286
+ <div>
287
+ <label class="block text-sm font-medium mb-1">URL</label>
288
+ <input type="text" placeholder="/api/endpoint" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
289
+ </div>
290
+ </div>
291
+ `
292
+ },
293
+ 'function': {
294
+ name: 'Function',
295
+ icon: 'code',
296
+ color: 'purple',
297
+ config: `
298
+ <div class="space-y-4">
299
+ <div>
300
+ <label class="block text-sm font-medium mb-1">Function Code</label>
301
+ <textarea class="w-full h-40 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 font-mono text-sm">// Write your JavaScript code here
302
+ // The 'msg' object contains the incoming message
303
+ // Return the modified message object
304
+
305
+ msg.payload = "Processed: " + msg.payload;
306
+ return msg;</textarea>
307
+ </div>
308
+ </div>
309
+ `
310
+ },
311
+ 'delay': {
312
+ name: 'Delay',
313
+ icon: 'clock',
314
+ color: 'purple',
315
+ config: `
316
+ <div class="space-y-4">
317
+ <div>
318
+ <label class="block text-sm font-medium mb-1">Delay (ms)</label>
319
+ <input type="number" value="1000" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
320
+ </div>
321
+ <div>
322
+ <label class="flex items-center">
323
+ <input type="checkbox" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400">
324
+ <span class="ml-2 text-sm">Drop messages while delayed</span>
325
+ </label>
326
+ </div>
327
+ </div>
328
+ `
329
+ },
330
+ 'debug': {
331
+ name: 'Debug',
332
+ icon: 'bug',
333
+ color: 'green',
334
+ config: `
335
+ <div class="space-y-4">
336
+ <div>
337
+ <label class="block text-sm font-medium mb-1">Output</label>
338
+ <select class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
339
+ <option>Complete message object</option>
340
+ <option>msg.payload</option>
341
+ <option>msg.topic</option>
342
+ </select>
343
+ </div>
344
+ <div>
345
+ <label class="block text-sm font-medium mb-1">To</label>
346
+ <select class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
347
+ <option>Debug console</option>
348
+ <option>System log</option>
349
+ </select>
350
+ </div>
351
+ </div>
352
+ `
353
+ },
354
+ 'http-out': {
355
+ name: 'HTTP Out',
356
+ icon: 'server',
357
+ color: 'green',
358
+ config: `
359
+ <div class="space-y-4">
360
+ <div>
361
+ <label class="block text-sm font-medium mb-1">Status Code</label>
362
+ <input type="number" value="200" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700">
363
+ </div>
364
+ <div>
365
+ <label class="block text-sm font-medium mb-1">Headers</label>
366
+ <textarea class="w-full h-20 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 font-mono text-sm">Content-Type: application/json</textarea>
367
+ </div>
368
+ </div>
369
+ `
370
+ }
371
+ };
372
+
373
+ // Initialize the app
374
+ function init() {
375
+ setupEventListeners();
376
+ updateCounters();
377
+ }
378
+
379
+ // Set up event listeners
380
+ function setupEventListeners() {
381
+ // Node panel drag events
382
+ document.querySelectorAll('.node-item').forEach(item => {
383
+ item.addEventListener('dragstart', handleNodeDragStart);
384
+ });
385
+
386
+ // Canvas events
387
+ flowCanvas.addEventListener('dragover', handleCanvasDragOver);
388
+ flowCanvas.addEventListener('drop', handleCanvasDrop);
389
+ flowCanvas.addEventListener('click', handleCanvasClick);
390
+
391
+ // Run flow button
392
+ runFlowBtn.addEventListener('click', runFlow);
393
+
394
+ // Close config panel
395
+ closeConfigBtn.addEventListener('click', closeConfigPanel);
396
+
397
+ // Keyboard events
398
+ document.addEventListener('keydown', handleKeyDown);
399
+ }
400
+
401
+ // Handle node drag start from panel
402
+ function handleNodeDragStart(e) {
403
+ const nodeType = e.target.getAttribute('data-type');
404
+ e.dataTransfer.setData('application/node-type', nodeType);
405
+ e.dataTransfer.effectAllowed = 'copy';
406
+ }
407
+
408
+ // Handle canvas drag over
409
+ function handleCanvasDragOver(e) {
410
+ e.preventDefault();
411
+ e.dataTransfer.dropEffect = 'copy';
412
+ }
413
+
414
+ // Handle canvas drop
415
+ function handleCanvasDrop(e) {
416
+ e.preventDefault();
417
+ const nodeType = e.dataTransfer.getData('application/node-type');
418
+ if (!nodeType) return;
419
+
420
+ const rect = flowCanvas.getBoundingClientRect();
421
+ const x = e.clientX - rect.left;
422
+ const y = e.clientY - rect.top;
423
+
424
+ createNode(nodeType, x, y);
425
+ }
426
+
427
+ // Create a new node
428
+ function createNode(type, x, y) {
429
+ const template = nodeTemplates[type];
430
+ if (!template) return;
431
+
432
+ const nodeId = state.nextNodeId++;
433
+ const node = {
434
+ id: nodeId,
435
+ type: type,
436
+ x: x,
437
+ y: y,
438
+ connections: {
439
+ inputs: [],
440
+ outputs: []
441
+ }
442
+ };
443
+
444
+ const nodeEl = document.createElement('div');
445
+ nodeEl.className = `node absolute w-48 bg-white dark:bg-gray-700 rounded-lg shadow-md border border-${template.color}-300 dark:border-${template.color}-800 cursor-move z-20`;
446
+ nodeEl.style.transform = `translate(${x}px, ${y}px)`;
447
+ nodeEl.dataset.nodeId = nodeId;
448
+
449
+ nodeEl.innerHTML = `
450
+ <div class="bg-${template.color}-100 dark:bg-${template.color}-900/50 px-3 py-2 rounded-t-lg flex justify-between items-center">
451
+ <div class="flex items-center">
452
+ <i class="fas fa-${template.icon} text-${template.color}-500 mr-2"></i>
453
+ <span class="font-medium">${template.name}</span>
454
+ </div>
455
+ <button class="node-delete-btn p-1 rounded-full hover:bg-${template.color}-200 dark:hover:bg-${template.color}-800/50 text-gray-500 dark:text-gray-300">
456
+ <i class="fas fa-times text-xs"></i>
457
+ </button>
458
+ </div>
459
+ <div class="p-3">
460
+ <div class="text-xs text-gray-500 dark:text-gray-400">${type}</div>
461
+ </div>
462
+ <div class="node-handle input absolute top-1/2 bg-${template.color}-500 border-2 border-white dark:border-gray-700"></div>
463
+ <div class="node-handle output absolute top-1/2 bg-${template.color}-500 border-2 border-white dark:border-gray-700"></div>
464
+ `;
465
+
466
+ flowCanvas.appendChild(nodeEl);
467
+ node.element = nodeEl;
468
+
469
+ // Add event listeners to the node
470
+ nodeEl.addEventListener('mousedown', startNodeDrag);
471
+ nodeEl.querySelector('.node-delete-btn').addEventListener('click', (e) => {
472
+ e.stopPropagation();
473
+ deleteNode(nodeId);
474
+ });
475
+
476
+ // Add connection handle events
477
+ const inputHandle = nodeEl.querySelector('.node-handle.input');
478
+ const outputHandle = nodeEl.querySelector('.node-handle.output');
479
+
480
+ inputHandle.addEventListener('mousedown', (e) => startConnection(e, nodeId, 'input'));
481
+ outputHandle.addEventListener('mousedown', (e) => startConnection(e, nodeId, 'output'));
482
+
483
+ state.nodes.push(node);
484
+ updateCounters();
485
+ }
486
+
487
+ // Start dragging a node
488
+ function startNodeDrag(e) {
489
+ if (e.target.classList.contains('node-delete-btn') ||
490
+ e.target.classList.contains('node-handle')) {
491
+ return;
492
+ }
493
+
494
+ const nodeEl = e.currentTarget;
495
+ const nodeId = parseInt(nodeEl.dataset.nodeId);
496
+ const node = state.nodes.find(n => n.id === nodeId);
497
+
498
+ if (!node) return;
499
+
500
+ state.draggingNode = node;
501
+ state.offset = {
502
+ x: e.clientX - node.x,
503
+ y: e.clientY - node.y
504
+ };
505
+
506
+ document.addEventListener('mousemove', dragNode);
507
+ document.addEventListener('mouseup', stopNodeDrag);
508
+ nodeEl.style.zIndex = '30';
509
+ nodeEl.style.cursor = 'grabbing';
510
+ }
511
+
512
+ // Drag a node
513
+ function dragNode(e) {
514
+ if (!state.draggingNode) return;
515
+
516
+ const node = state.draggingNode;
517
+ const nodeEl = node.element;
518
+
519
+ node.x = e.clientX - state.offset.x;
520
+ node.y = e.clientY - state.offset.y;
521
+
522
+ nodeEl.style.transform = `translate(${node.x}px, ${node.y}px)`;
523
+
524
+ // Update all connections for this node
525
+ updateNodeConnections(node);
526
+ }
527
+
528
+ // Stop dragging a node
529
+ function stopNodeDrag() {
530
+ if (!state.draggingNode) return;
531
+
532
+ const node = state.draggingNode;
533
+ const nodeEl = node.element;
534
+
535
+ document.removeEventListener('mousemove', dragNode);
536
+ document.removeEventListener('mouseup', stopNodeDrag);
537
+ nodeEl.style.zIndex = '20';
538
+ nodeEl.style.cursor = 'move';
539
+
540
+ state.draggingNode = null;
541
+ }
542
+
543
+ // Delete a node
544
+ function deleteNode(nodeId) {
545
+ // Remove all connections to/from this node
546
+ const connectionsToRemove = state.connections.filter(conn =>
547
+ conn.sourceId === nodeId || conn.targetId === nodeId
548
+ );
549
+
550
+ connectionsToRemove.forEach(conn => {
551
+ deleteConnection(conn.id);
552
+ });
553
+
554
+ // Remove the node
555
+ const nodeIndex = state.nodes.findIndex(n => n.id === nodeId);
556
+ if (nodeIndex !== -1) {
557
+ const node = state.nodes[nodeIndex];
558
+ node.element.remove();
559
+ state.nodes.splice(nodeIndex, 1);
560
+ }
561
+
562
+ if (state.selectedNode && state.selectedNode.id === nodeId) {
563
+ closeConfigPanel();
564
+ }
565
+
566
+ updateCounters();
567
+ }
568
+
569
+ // Start creating a connection
570
+ function startConnection(e, nodeId, handleType) {
571
+ e.stopPropagation();
572
+
573
+ const node = state.nodes.find(n => n.id === nodeId);
574
+ if (!node) return;
575
+
576
+ state.creatingConnection = {
577
+ nodeId: nodeId,
578
+ handleType: handleType,
579
+ startX: e.clientX,
580
+ startY: e.clientY
581
+ };
582
+
583
+ // Create a temporary connection line
584
+ state.tempConnection = document.createElementNS('http://www.w3.org/2000/svg', 'path');
585
+ state.tempConnection.classList.add('connection-line', 'temp');
586
+ state.tempConnection.setAttribute('stroke', '#6b7280');
587
+ connectionsSvg.appendChild(state.tempConnection);
588
+
589
+ document.addEventListener('mousemove', updateTempConnection);
590
+ document.addEventListener('mouseup', finishConnection);
591
+ }
592
+
593
+ // Update temporary connection while dragging
594
+ function updateTempConnection(e) {
595
+ if (!state.creatingConnection || !state.tempConnection) return;
596
+
597
+ const node = state.nodes.find(n => n.id === state.creatingConnection.nodeId);
598
+ if (!node) return;
599
+
600
+ const nodeEl = node.element;
601
+ const rect = nodeEl.getBoundingClientRect();
602
+ const canvasRect = flowCanvas.getBoundingClientRect();
603
+ const scrollLeft = flowCanvas.scrollLeft;
604
+ const scrollTop = flowCanvas.scrollTop;
605
+
606
+ let startX, startY;
607
+ if (state.creatingConnection.handleType === 'input') {
608
+ startX = rect.left - canvasRect.left + scrollLeft;
609
+ startY = rect.top - canvasRect.top + rect.height / 2 + scrollTop;
610
+ } else {
611
+ startX = rect.left - canvasRect.left + rect.width + scrollLeft;
612
+ startY = rect.top - canvasRect.top + rect.height / 2 + scrollTop;
613
+ }
614
+
615
+ const endX = e.clientX - canvasRect.left + scrollLeft;
616
+ const endY = e.clientY - canvasRect.top + scrollTop;
617
+
618
+ // Calculate the control points for the bezier curve
619
+ const midX = (startX + endX) / 2;
620
+ const ctrlX1 = midX;
621
+ const ctrlX2 = midX;
622
+
623
+ // Create the path data
624
+ const pathData = `M${startX},${startY} C${ctrlX1},${startY} ${ctrlX2},${endY} ${endX},${endY}`;
625
+ state.tempConnection.setAttribute('d', pathData);
626
+ }
627
+
628
+ // Finish creating a connection
629
+ function finishConnection(e) {
630
+ if (!state.creatingConnection) return;
631
+
632
+ document.removeEventListener('mousemove', updateTempConnection);
633
+ document.removeEventListener('mouseup', finishConnection);
634
+
635
+ // Remove the temporary connection line
636
+ if (state.tempConnection) {
637
+ connectionsSvg.removeChild(state.tempConnection);
638
+ state.tempConnection = null;
639
+ }
640
+
641
+ // Check if we're hovering over a node handle
642
+ const canvasRect = flowCanvas.getBoundingClientRect();
643
+ const scrollLeft = flowCanvas.scrollLeft;
644
+ const scrollTop = flowCanvas.scrollTop;
645
+ const canvasX = e.clientX - canvasRect.left + scrollLeft;
646
+ const canvasY = e.clientY - canvasRect.top + scrollTop;
647
+
648
+ const elements = document.elementsFromPoint(e.clientX, e.clientY);
649
+ const targetHandle = elements.find(el => el.classList.contains('node-handle'));
650
+
651
+ if (!targetHandle) {
652
+ state.creatingConnection = null;
653
+ return;
654
+ }
655
+
656
+ const targetNodeEl = targetHandle.closest('.node');
657
+ if (!targetNodeEl) {
658
+ state.creatingConnection = null;
659
+ return;
660
+ }
661
+
662
+ const targetNodeId = parseInt(targetNodeEl.dataset.nodeId);
663
+ const targetNode = state.nodes.find(n => n.id === targetNodeId);
664
+ if (!targetNode) {
665
+ state.creatingConnection = null;
666
+ return;
667
+ }
668
+
669
+ // Determine if this is an input or output handle
670
+ const targetHandleType = targetHandle.classList.contains('input') ? 'input' : 'output';
671
+
672
+ // Create the connection if valid
673
+ if (state.creatingConnection.handleType === 'output' && targetHandleType === 'input') {
674
+ // Output to input connection
675
+ createConnection(state.creatingConnection.nodeId, targetNodeId);
676
+ } else if (state.creatingConnection.handleType === 'input' && targetHandleType === 'output') {
677
+ // Input to output connection (reverse)
678
+ createConnection(targetNodeId, state.creatingConnection.nodeId);
679
+ }
680
+
681
+ state.creatingConnection = null;
682
+ }
683
+
684
+ // Create a connection between two nodes
685
+ function createConnection(sourceId, targetId) {
686
+ // Don't allow connections to self
687
+ if (sourceId === targetId) return;
688
+
689
+ // Check if connection already exists
690
+ const existingConnection = state.connections.find(conn =>
691
+ conn.sourceId === sourceId && conn.targetId === targetId
692
+ );
693
+
694
+ if (existingConnection) return;
695
+
696
+ const connectionId = state.nextConnectionId++;
697
+ const connection = {
698
+ id: connectionId,
699
+ sourceId: sourceId,
700
+ targetId: targetId,
701
+ element: null
702
+ };
703
+
704
+ // Add to source and target nodes
705
+ const sourceNode = state.nodes.find(n => n.id === sourceId);
706
+ const targetNode = state.nodes.find(n => n.id === targetId);
707
+
708
+ if (sourceNode && targetNode) {
709
+ sourceNode.connections.outputs.push(connectionId);
710
+ targetNode.connections.inputs.push(connectionId);
711
+ }
712
+
713
+ // Create the SVG path element
714
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
715
+ path.classList.add('connection-line');
716
+ path.setAttribute('stroke', '#3b82f6');
717
+ path.setAttribute('marker-end', 'url(#arrowhead)');
718
+ connectionsSvg.appendChild(path);
719
+
720
+ // Create the delete button
721
+ const deleteBtn = document.createElement('div');
722
+ deleteBtn.classList.add('connection-delete-btn', 'absolute', 'bg-white', 'dark:bg-gray-700', 'rounded-full', 'w-5', 'h-5', 'flex', 'items-center', 'justify-center', 'shadow-sm', 'border', 'border-gray-300', 'dark:border-gray-600', 'cursor-pointer', 'z-30');
723
+ deleteBtn.innerHTML = '<i class="fas fa-times text-xs text-gray-600 dark:text-gray-300"></i>';
724
+ deleteBtn.addEventListener('click', (e) => {
725
+ e.stopPropagation();
726
+ deleteConnection(connectionId);
727
+ });
728
+ flowCanvas.appendChild(deleteBtn);
729
+
730
+ connection.path = path;
731
+ connection.deleteBtn = deleteBtn;
732
+ state.connections.push(connection);
733
+
734
+ // Update the connection position
735
+ updateConnection(connection);
736
+
737
+ updateCounters();
738
+ }
739
+
740
+ // Update a connection's position
741
+ function updateConnection(connection) {
742
+ const sourceNode = state.nodes.find(n => n.id === connection.sourceId);
743
+ const targetNode = state.nodes.find(n => n.id === connection.targetId);
744
+
745
+ if (!sourceNode || !targetNode) return;
746
+
747
+ const sourceEl = sourceNode.element;
748
+ const targetEl = targetNode.element;
749
+
750
+ const sourceRect = sourceEl.getBoundingClientRect();
751
+ const targetRect = targetEl.getBoundingClientRect();
752
+ const canvasRect = flowCanvas.getBoundingClientRect();
753
+
754
+ // Calculate positions relative to the canvas
755
+ const sourceX = sourceRect.left + sourceRect.width - canvasRect.left;
756
+ const sourceY = sourceRect.top + sourceRect.height / 2 - canvasRect.top;
757
+
758
+ const targetX = targetRect.left - canvasRect.left;
759
+ const targetY = targetRect.top + targetRect.height / 2 - canvasRect.top;
760
+
761
+ // Calculate the control points for the bezier curve
762
+ const midX = (sourceX + targetX) / 2;
763
+ const ctrlX1 = midX;
764
+ const ctrlX2 = midX;
765
+
766
+ // Create the path data
767
+ const pathData = `M${sourceX},${sourceY} C${ctrlX1},${sourceY} ${ctrlX2},${targetY} ${targetX},${targetY}`;
768
+ connection.path.setAttribute('d', pathData);
769
+
770
+ // Position the delete button at the midpoint
771
+ const btnX = (sourceX + targetX) / 2 - 10;
772
+ const btnY = (sourceY + targetY) / 2 - 10;
773
+
774
+ connection.deleteBtn.style.left = `${btnX}px`;
775
+ connection.deleteBtn.style.top = `${btnY}px`;
776
+ }
777
+
778
+ // Update all connections for a node
779
+ function updateNodeConnections(node) {
780
+ // Output connections
781
+ node.connections.outputs.forEach(connId => {
782
+ const connection = state.connections.find(c => c.id === connId);
783
+ if (connection) updateConnection(connection);
784
+ });
785
+
786
+ // Input connections
787
+ node.connections.inputs.forEach(connId => {
788
+ const connection = state.connections.find(c => c.id === connId);
789
+ if (connection) updateConnection(connection);
790
+ });
791
+ }
792
+
793
+ // Delete a connection
794
+ function deleteConnection(connectionId) {
795
+ const connectionIndex = state.connections.findIndex(c => c.id === connectionId);
796
+ if (connectionIndex === -1) return;
797
+
798
+ const connection = state.connections[connectionIndex];
799
+
800
+ // Remove from source and target nodes
801
+ const sourceNode = state.nodes.find(n => n.id === connection.sourceId);
802
+ const targetNode = state.nodes.find(n => n.id === connection.targetId);
803
+
804
+ if (sourceNode) {
805
+ const outputIndex = sourceNode.connections.outputs.indexOf(connectionId);
806
+ if (outputIndex !== -1) {
807
+ sourceNode.connections.outputs.splice(outputIndex, 1);
808
+ }
809
+ }
810
+
811
+ if (targetNode) {
812
+ const inputIndex = targetNode.connections.inputs.indexOf(connectionId);
813
+ if (inputIndex !== -1) {
814
+ targetNode.connections.inputs.splice(inputIndex, 1);
815
+ }
816
+ }
817
+
818
+ // Remove DOM elements
819
+ if (connection.path) {
820
+ connectionsSvg.removeChild(connection.path);
821
+ }
822
+
823
+ if (connection.deleteBtn) {
824
+ connection.deleteBtn.remove();
825
+ }
826
+
827
+ // Remove from state
828
+ state.connections.splice(connectionIndex, 1);
829
+
830
+ updateCounters();
831
+ }
832
+
833
+ // Handle canvas click
834
+ function handleCanvasClick(e) {
835
+ // Check if we clicked on a node
836
+ const nodeEl = e.target.closest('.node');
837
+ if (nodeEl) {
838
+ const nodeId = parseInt(nodeEl.dataset.nodeId);
839
+ const node = state.nodes.find(n => n.id === nodeId);
840
+ if (node) {
841
+ selectNode(node);
842
+ }
843
+ } else {
844
+ // Clicked on empty space, deselect
845
+ closeConfigPanel();
846
+ }
847
+ }
848
+
849
+ // Select a node and show its config
850
+ function selectNode(node) {
851
+ state.selectedNode = node;
852
+
853
+ // Highlight the selected node
854
+ document.querySelectorAll('.node').forEach(el => {
855
+ el.classList.remove('ring-2', 'ring-blue-500');
856
+ });
857
+
858
+ node.element.classList.add('ring-2', 'ring-blue-500');
859
+
860
+ // Show the config panel
861
+ showConfigPanel(node);
862
+ }
863
+
864
+ // Show config panel for a node
865
+ function showConfigPanel(node) {
866
+ const template = nodeTemplates[node.type];
867
+ if (!template) return;
868
+
869
+ nodeConfigContent.innerHTML = `
870
+ <div class="mb-4">
871
+ <h3 class="font-bold text-lg flex items-center">
872
+ <i class="fas fa-${template.icon} text-${template.color}-500 mr-2"></i>
873
+ ${template.name}
874
+ </h3>
875
+ <div class="text-xs text-gray-500 dark:text-gray-400">ID: ${node.id}</div>
876
+ </div>
877
+ ${template.config}
878
+ `;
879
+
880
+ // Add event listeners for any dynamic elements in the config
881
+ const repeatCheckbox = nodeConfigContent.querySelector('input[type="checkbox"]');
882
+ if (repeatCheckbox) {
883
+ repeatCheckbox.addEventListener('change', (e) => {
884
+ const repeatOptions = nodeConfigContent.querySelector('.repeat-options');
885
+ if (repeatOptions) {
886
+ repeatOptions.classList.toggle('hidden', !e.target.checked);
887
+ }
888
+ });
889
+ }
890
+
891
+ configPanel.classList.remove('hidden');
892
+ configPanel.classList.add('visible');
893
+ }
894
+
895
+ // Close config panel
896
+ function closeConfigPanel() {
897
+ if (state.selectedNode) {
898
+ state.selectedNode.element.classList.remove('ring-2', 'ring-blue-500');
899
+ state.selectedNode = null;
900
+ }
901
+
902
+ configPanel.classList.remove('visible');
903
+ configPanel.classList.add('hidden');
904
+ }
905
+
906
+ // Handle keyboard events
907
+ function handleKeyDown(e) {
908
+ // Delete key
909
+ if (e.key === 'Delete' && state.selectedNode) {
910
+ deleteNode(state.selectedNode.id);
911
+ }
912
+ }
913
+
914
+ // Run the flow
915
+ function runFlow() {
916
+ if (state.nodes.length === 0) {
917
+ statusMessageEl.textContent = 'No nodes to run';
918
+ return;
919
+ }
920
+
921
+ statusMessageEl.textContent = 'Running flow...';
922
+
923
+ // Simulate flow execution
924
+ setTimeout(() => {
925
+ statusMessageEl.textContent = 'Flow completed successfully';
926
+
927
+ // Reset after 2 seconds
928
+ setTimeout(() => {
929
+ statusMessageEl.textContent = 'Ready';
930
+ }, 2000);
931
+ }, 1500);
932
+ }
933
+
934
+ // Update node and connection counters
935
+ function updateCounters() {
936
+ nodeCountEl.textContent = `${state.nodes.length} node${state.nodes.length !== 1 ? 's' : ''}`;
937
+ connectionCountEl.textContent = `${state.connections.length} connection${state.connections.length !== 1 ? 's' : ''}`;
938
+ }
939
+
940
+ // Initialize the app
941
+ init();
942
+
943
+ // Add arrowhead marker to SVG for connection lines
944
+ const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
945
+ const marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
946
+ marker.setAttribute('id', 'arrowhead');
947
+ marker.setAttribute('markerWidth', '10');
948
+ marker.setAttribute('markerHeight', '7');
949
+ marker.setAttribute('refX', '9');
950
+ marker.setAttribute('refY', '3.5');
951
+ marker.setAttribute('orient', 'auto');
952
+
953
+ const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
954
+ polygon.setAttribute('points', '0 0, 10 3.5, 0 7');
955
+ polygon.setAttribute('fill', '#3b82f6');
956
+
957
+ marker.appendChild(polygon);
958
+ defs.appendChild(marker);
959
+ connectionsSvg.appendChild(defs);
960
+ });
961
+ </script>
962
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=snapo/nodesworkflow" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
963
+ </html>