LukasBe commited on
Commit
dd2f906
·
verified ·
1 Parent(s): 056a965

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +1426 -19
  3. prompts.txt +2 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Visual Agentic Editor
3
- emoji: 📚
4
- colorFrom: blue
5
- colorTo: pink
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: visual-agentic-editor
3
+ emoji: 🐳
4
+ colorFrom: yellow
5
+ colorTo: purple
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,1426 @@
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>NovaFlow | Visual Agentic Editor</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
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
11
+
12
+ body {
13
+ font-family: 'Poppins', sans-serif;
14
+ background-color: #0f172a;
15
+ color: #e2e8f0;
16
+ overflow: hidden;
17
+ }
18
+
19
+ .gradient-bg {
20
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
21
+ }
22
+
23
+ .node {
24
+ position: absolute;
25
+ min-width: 200px;
26
+ background: #1e293b;
27
+ border-radius: 12px;
28
+ border: 1px solid #334155;
29
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
30
+ cursor: grab;
31
+ user-select: none;
32
+ transition: all 0.2s ease;
33
+ }
34
+
35
+ .node:hover {
36
+ box-shadow: 0 0 0 2px #3b82f6;
37
+ }
38
+
39
+ .node.selected {
40
+ box-shadow: 0 0 0 2px #3b82f6;
41
+ border-color: #3b82f6;
42
+ }
43
+
44
+ .node-header {
45
+ padding: 10px 12px;
46
+ border-bottom: 1px solid #334155;
47
+ font-weight: 500;
48
+ display: flex;
49
+ justify-content: space-between;
50
+ align-items: center;
51
+ background: rgba(30, 41, 59, 0.8);
52
+ border-radius: 12px 12px 0 0;
53
+ }
54
+
55
+ .node-content {
56
+ padding: 12px;
57
+ }
58
+
59
+ .connector {
60
+ width: 16px;
61
+ height: 16px;
62
+ border-radius: 50%;
63
+ background: #334155;
64
+ border: 2px solid #64748b;
65
+ cursor: pointer;
66
+ position: absolute;
67
+ z-index: 10;
68
+ }
69
+
70
+ .connector:hover {
71
+ background: #3b82f6;
72
+ border-color: #3b82f6;
73
+ }
74
+
75
+ .connector.input {
76
+ left: -8px;
77
+ }
78
+
79
+ .connector.output {
80
+ right: -8px;
81
+ }
82
+
83
+ .connection {
84
+ position: absolute;
85
+ pointer-events: none;
86
+ z-index: 5;
87
+ }
88
+
89
+ .connection-path {
90
+ stroke: #64748b;
91
+ stroke-width: 2;
92
+ fill: none;
93
+ }
94
+
95
+ .connection-path.active {
96
+ stroke: #3b82f6;
97
+ stroke-width: 3;
98
+ }
99
+
100
+ .node-icon {
101
+ width: 24px;
102
+ height: 24px;
103
+ border-radius: 6px;
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ margin-right: 8px;
108
+ flex-shrink: 0;
109
+ }
110
+
111
+ .node-title {
112
+ white-space: nowrap;
113
+ overflow: hidden;
114
+ text-overflow: ellipsis;
115
+ flex-grow: 1;
116
+ }
117
+
118
+ .node-actions {
119
+ display: flex;
120
+ gap: 4px;
121
+ }
122
+
123
+ .node-action-btn {
124
+ width: 20px;
125
+ height: 20px;
126
+ border-radius: 4px;
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ color: #94a3b8;
131
+ cursor: pointer;
132
+ }
133
+
134
+ .node-action-btn:hover {
135
+ background: #334155;
136
+ color: #e2e8f0;
137
+ }
138
+
139
+ .palette-item {
140
+ padding: 8px 12px;
141
+ border-radius: 6px;
142
+ margin-bottom: 8px;
143
+ cursor: grab;
144
+ background: #1e293b;
145
+ border: 1px solid #334155;
146
+ display: flex;
147
+ align-items: center;
148
+ }
149
+
150
+ .palette-item:hover {
151
+ background: #334155;
152
+ }
153
+
154
+ .glow {
155
+ box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
156
+ }
157
+
158
+ .slide-in {
159
+ animation: slideIn 0.3s ease-out forwards;
160
+ }
161
+
162
+ @keyframes slideIn {
163
+ from { transform: translateY(20px); opacity: 0; }
164
+ to { transform: translateY(0); opacity: 1; }
165
+ }
166
+
167
+ .fade-in {
168
+ animation: fadeIn 0.5s ease-in forwards;
169
+ }
170
+
171
+ @keyframes fadeIn {
172
+ from { opacity: 0; }
173
+ to { opacity: 1; }
174
+ }
175
+
176
+ /* Custom scrollbar */
177
+ ::-webkit-scrollbar {
178
+ width: 8px;
179
+ height: 8px;
180
+ }
181
+
182
+ ::-webkit-scrollbar-track {
183
+ background: #1e293b;
184
+ }
185
+
186
+ ::-webkit-scrollbar-thumb {
187
+ background: #334155;
188
+ border-radius: 4px;
189
+ }
190
+
191
+ ::-webkit-scrollbar-thumb:hover {
192
+ background: #475569;
193
+ }
194
+
195
+ /* Context menu */
196
+ .context-menu {
197
+ position: absolute;
198
+ background: #1e293b;
199
+ border: 1px solid #334155;
200
+ border-radius: 8px;
201
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
202
+ z-index: 100;
203
+ min-width: 160px;
204
+ overflow: hidden;
205
+ }
206
+
207
+ .context-menu-item {
208
+ padding: 8px 12px;
209
+ cursor: pointer;
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 8px;
213
+ }
214
+
215
+ .context-menu-item:hover {
216
+ background: #334155;
217
+ }
218
+
219
+ /* Tooltip */
220
+ .tooltip {
221
+ position: absolute;
222
+ background: #1e293b;
223
+ border: 1px solid #334155;
224
+ border-radius: 4px;
225
+ padding: 4px 8px;
226
+ font-size: 12px;
227
+ pointer-events: none;
228
+ z-index: 100;
229
+ white-space: nowrap;
230
+ }
231
+
232
+ /* Status bar */
233
+ .status-bar {
234
+ height: 24px;
235
+ background: #1e293b;
236
+ border-top: 1px solid #334155;
237
+ display: flex;
238
+ align-items: center;
239
+ padding: 0 8px;
240
+ font-size: 12px;
241
+ color: #94a3b8;
242
+ }
243
+
244
+ /* Zoom controls */
245
+ .zoom-controls {
246
+ position: absolute;
247
+ bottom: 32px;
248
+ right: 16px;
249
+ background: #1e293b;
250
+ border: 1px solid #334155;
251
+ border-radius: 8px;
252
+ overflow: hidden;
253
+ z-index: 10;
254
+ }
255
+
256
+ .zoom-btn {
257
+ width: 32px;
258
+ height: 32px;
259
+ display: flex;
260
+ align-items: center;
261
+ justify-content: center;
262
+ cursor: pointer;
263
+ }
264
+
265
+ .zoom-btn:hover {
266
+ background: #334155;
267
+ }
268
+
269
+ /* Node property editor */
270
+ .property-editor {
271
+ background: #1e293b;
272
+ border-left: 1px solid #334155;
273
+ height: 100%;
274
+ overflow-y: auto;
275
+ }
276
+
277
+ .property-group {
278
+ border-bottom: 1px solid #334155;
279
+ padding: 12px;
280
+ }
281
+
282
+ .property-group-title {
283
+ font-weight: 500;
284
+ margin-bottom: 8px;
285
+ display: flex;
286
+ align-items: center;
287
+ justify-content: space-between;
288
+ }
289
+
290
+ .property-row {
291
+ margin-bottom: 12px;
292
+ }
293
+
294
+ .property-label {
295
+ font-size: 12px;
296
+ color: #94a3b8;
297
+ margin-bottom: 4px;
298
+ }
299
+
300
+ .property-input {
301
+ width: 100%;
302
+ background: #334155;
303
+ border: 1px solid #475569;
304
+ border-radius: 4px;
305
+ padding: 6px 8px;
306
+ color: #e2e8f0;
307
+ }
308
+
309
+ .property-input:focus {
310
+ outline: none;
311
+ border-color: #3b82f6;
312
+ }
313
+
314
+ /* Tabs */
315
+ .tabs {
316
+ display: flex;
317
+ border-bottom: 1px solid #334155;
318
+ }
319
+
320
+ .tab {
321
+ padding: 8px 16px;
322
+ cursor: pointer;
323
+ border-bottom: 2px solid transparent;
324
+ }
325
+
326
+ .tab.active {
327
+ border-bottom-color: #3b82f6;
328
+ color: #3b82f6;
329
+ }
330
+
331
+ .tab:hover:not(.active) {
332
+ background: #334155;
333
+ }
334
+
335
+ /* JSON editor */
336
+ .json-editor {
337
+ width: 100%;
338
+ height: 200px;
339
+ background: #334155;
340
+ border: 1px solid #475569;
341
+ border-radius: 4px;
342
+ padding: 8px;
343
+ font-family: monospace;
344
+ color: #e2e8f0;
345
+ resize: vertical;
346
+ }
347
+
348
+ /* Dragging state */
349
+ .dragging-connection {
350
+ position: absolute;
351
+ pointer-events: none;
352
+ z-index: 5;
353
+ }
354
+
355
+ .dragging-connection-path {
356
+ stroke: #3b82f6;
357
+ stroke-width: 2;
358
+ fill: none;
359
+ }
360
+ </style>
361
+ </head>
362
+ <body class="min-h-screen gradient-bg">
363
+ <div class="flex flex-col h-screen">
364
+ <!-- Header -->
365
+ <header class="py-3 px-6 flex justify-between items-center border-b border-slate-700">
366
+ <div class="flex items-center space-x-3">
367
+ <div class="w-10 h-10 rounded-full bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center">
368
+ <i class="fas fa-project-diagram text-white text-lg"></i>
369
+ </div>
370
+ <h1 class="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400">
371
+ NovaFlow
372
+ </h1>
373
+ <div class="text-sm text-slate-400">Visual Agentic Editor</div>
374
+ </div>
375
+ <div class="flex space-x-4">
376
+ <button id="apiSettingsBtn" class="px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg text-sm font-medium transition-colors flex items-center">
377
+ <i class="fas fa-key mr-2"></i> API Settings
378
+ </button>
379
+ <button id="runWorkflowBtn" class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-lg text-sm font-medium transition-colors flex items-center">
380
+ <i class="fas fa-play mr-2"></i> Run Workflow
381
+ </button>
382
+ <button id="saveWorkflowBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-sm font-medium transition-colors flex items-center">
383
+ <i class="fas fa-save mr-2"></i> Save
384
+ </button>
385
+ </div>
386
+ </header>
387
+
388
+ <!-- Main Content -->
389
+ <main class="flex-1 flex overflow-hidden">
390
+ <!-- Left Sidebar - Node Palette -->
391
+ <aside class="w-64 bg-slate-800 border-r border-slate-700 overflow-y-auto flex flex-col">
392
+ <div class="p-4 border-b border-slate-700">
393
+ <h2 class="text-sm uppercase font-semibold text-slate-400 mb-3">Node Palette</h2>
394
+ <input type="text" placeholder="Search nodes..." class="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-sm mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500">
395
+ </div>
396
+
397
+ <div class="flex-1 overflow-y-auto p-4">
398
+ <div class="mb-6">
399
+ <h3 class="text-xs uppercase font-semibold text-slate-400 mb-2 flex items-center">
400
+ <i class="fas fa-brain mr-2"></i> AI Actions
401
+ </h3>
402
+ <div id="aiNodes">
403
+ <div class="palette-item" draggable="true" data-type="llm">
404
+ <div class="node-icon bg-blue-900 text-blue-300">
405
+ <i class="fas fa-robot"></i>
406
+ </div>
407
+ <span>LLM Prompt</span>
408
+ </div>
409
+ <div class="palette-item" draggable="true" data-type="code">
410
+ <div class="node-icon bg-purple-900 text-purple-300">
411
+ <i class="fas fa-code"></i>
412
+ </div>
413
+ <span>Code Execution</span>
414
+ </div>
415
+ <div class="palette-item" draggable="true" data-type="decision">
416
+ <div class="node-icon bg-green-900 text-green-300">
417
+ <i class="fas fa-code-branch"></i>
418
+ </div>
419
+ <span>Decision</span>
420
+ </div>
421
+ </div>
422
+ </div>
423
+
424
+ <div class="mb-6">
425
+ <h3 class="text-xs uppercase font-semibold text-slate-400 mb-2 flex items-center">
426
+ <i class="fas fa-exchange-alt mr-2"></i> Data Processing
427
+ </h3>
428
+ <div id="dataNodes">
429
+ <div class="palette-item" draggable="true" data-type="transform">
430
+ <div class="node-icon bg-yellow-900 text-yellow-300">
431
+ <i class="fas fa-sliders-h"></i>
432
+ </div>
433
+ <span>Data Transform</span>
434
+ </div>
435
+ <div class="palette-item" draggable="true" data-type="filter">
436
+ <div class="node-icon bg-red-900 text-red-300">
437
+ <i class="fas fa-filter"></i>
438
+ </div>
439
+ <span>Filter</span>
440
+ </div>
441
+ <div class="palette-item" draggable="true" data-type="join">
442
+ <div class="node-icon bg-indigo-900 text-indigo-300">
443
+ <i class="fas fa-link"></i>
444
+ </div>
445
+ <span>Join Data</span>
446
+ </div>
447
+ </div>
448
+ </div>
449
+
450
+ <div>
451
+ <h3 class="text-xs uppercase font-semibold text-slate-400 mb-2 flex items-center">
452
+ <i class="fas fa-plug mr-2"></i> Integrations
453
+ </h3>
454
+ <div id="integrationNodes">
455
+ <div class="palette-item" draggable="true" data-type="api">
456
+ <div class="node-icon bg-pink-900 text-pink-300">
457
+ <i class="fas fa-cloud"></i>
458
+ </div>
459
+ <span>API Call</span>
460
+ </div>
461
+ <div class="palette-item" draggable="true" data-type="webhook">
462
+ <div class="node-icon bg-teal-900 text-teal-300">
463
+ <i class="fas fa-bell"></i>
464
+ </div>
465
+ <span>Webhook</span>
466
+ </div>
467
+ <div class="palette-item" draggable="true" data-type="database">
468
+ <div class="node-icon bg-amber-900 text-amber-300">
469
+ <i class="fas fa-database"></i>
470
+ </div>
471
+ <span>Database Query</span>
472
+ </div>
473
+ </div>
474
+ </div>
475
+ </div>
476
+ </aside>
477
+
478
+ <!-- Canvas Area -->
479
+ <section class="flex-1 relative overflow-hidden" id="canvasContainer">
480
+ <div id="canvas" class="absolute w-full h-full bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDIwIDIwIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxyZWN0IHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0iIzBmMTcyYSIvPjxwYXRoIGQ9Ik0xMCAxMHYxTTEwIDEwSDEwVjEwWiIgc3Ryb2tlPSIjMzM0MTU1IiBzdHJva2Utd2lkdGg9IjEiLz48L2c+PC9zdmc+')]">
481
+ <!-- Nodes will be added here dynamically -->
482
+ </div>
483
+
484
+ <!-- Zoom controls -->
485
+ <div class="zoom-controls">
486
+ <div class="zoom-btn" id="zoomInBtn">
487
+ <i class="fas fa-search-plus"></i>
488
+ </div>
489
+ <div class="zoom-btn border-t border-slate-700" id="zoomOutBtn">
490
+ <i class="fas fa-search-minus"></i>
491
+ </div>
492
+ <div class="zoom-btn border-t border-slate-700" id="zoomResetBtn">
493
+ <i class="fas fa-expand"></i>
494
+ </div>
495
+ </div>
496
+ </section>
497
+
498
+ <!-- Right Sidebar - Property Editor -->
499
+ <aside class="w-80 bg-slate-800 border-l border-slate-700 overflow-y-auto flex flex-col">
500
+ <div class="tabs">
501
+ <div class="tab active" data-tab="properties">Properties</div>
502
+ <div class="tab" data-tab="workflow">Workflow</div>
503
+ </div>
504
+
505
+ <div class="property-editor">
506
+ <div id="propertiesTab" class="tab-content active">
507
+ <div class="property-group">
508
+ <div class="property-group-title">
509
+ <span>Node Properties</span>
510
+ <i class="fas fa-info-circle text-slate-500"></i>
511
+ </div>
512
+ <div class="property-row">
513
+ <div class="property-label">Node Name</div>
514
+ <input type="text" class="property-input" id="nodeNameInput" value="LLM Prompt">
515
+ </div>
516
+ <div class="property-row">
517
+ <div class="property-label">Node Type</div>
518
+ <input type="text" class="property-input" id="nodeTypeInput" value="llm" disabled>
519
+ </div>
520
+ </div>
521
+
522
+ <div class="property-group">
523
+ <div class="property-group-title">
524
+ <span>Prompt Configuration</span>
525
+ </div>
526
+ <div class="property-row">
527
+ <div class="property-label">Model</div>
528
+ <select class="property-input" id="modelSelect">
529
+ <option value="gpt-3.5-turbo">GPT-3.5 Turbo</option>
530
+ <option value="gpt-4">GPT-4</option>
531
+ </select>
532
+ </div>
533
+ <div class="property-row">
534
+ <div class="property-label">Temperature</div>
535
+ <input type="range" class="w-full" id="temperatureSlider" min="0" max="1" step="0.1" value="0.7">
536
+ <div class="flex justify-between text-xs text-slate-400 mt-1">
537
+ <span>Precise</span>
538
+ <span>Balanced</span>
539
+ <span>Creative</span>
540
+ </div>
541
+ </div>
542
+ <div class="property-row">
543
+ <div class="property-label">System Prompt</div>
544
+ <textarea class="property-input" id="systemPromptInput" rows="3"></textarea>
545
+ </div>
546
+ <div class="property-row">
547
+ <div class="property-label">User Prompt</div>
548
+ <textarea class="property-input" id="userPromptInput" rows="3"></textarea>
549
+ </div>
550
+ </div>
551
+
552
+ <div class="property-group">
553
+ <div class="property-group-title">
554
+ <span>Input/Output</span>
555
+ </div>
556
+ <div class="property-row">
557
+ <div class="property-label">Input Variables</div>
558
+ <input type="text" class="property-input" id="inputVarsInput" placeholder="var1, var2, ...">
559
+ </div>
560
+ <div class="property-row">
561
+ <div class="property-label">Output Variable</div>
562
+ <input type="text" class="property-input" id="outputVarInput" placeholder="result">
563
+ </div>
564
+ </div>
565
+ </div>
566
+
567
+ <div id="workflowTab" class="tab-content hidden">
568
+ <div class="property-group">
569
+ <div class="property-group-title">
570
+ <span>Workflow Settings</span>
571
+ </div>
572
+ <div class="property-row">
573
+ <div class="property-label">Workflow Name</div>
574
+ <input type="text" class="property-input" id="workflowNameInput" value="My Agentic Workflow">
575
+ </div>
576
+ <div class="property-row">
577
+ <div class="property-label">Description</div>
578
+ <textarea class="property-input" id="workflowDescInput" rows="2"></textarea>
579
+ </div>
580
+ </div>
581
+
582
+ <div class="property-group">
583
+ <div class="property-group-title">
584
+ <span>Variables</span>
585
+ <button class="text-xs text-blue-400">+ Add</button>
586
+ </div>
587
+ <div class="property-row">
588
+ <textarea class="json-editor" id="variablesEditor">{
589
+ "variables": {
590
+ "exampleVar": "default value"
591
+ }
592
+ }</textarea>
593
+ </div>
594
+ </div>
595
+
596
+ <div class="property-group">
597
+ <div class="property-group-title">
598
+ <span>Workflow JSON</span>
599
+ <button class="text-xs text-blue-400">Export</button>
600
+ </div>
601
+ <div class="property-row">
602
+ <textarea class="json-editor" id="workflowEditor">{
603
+ "nodes": [],
604
+ "connections": []
605
+ }</textarea>
606
+ </div>
607
+ </div>
608
+ </div>
609
+ </div>
610
+ </aside>
611
+ </main>
612
+
613
+ <!-- Status Bar -->
614
+ <footer class="status-bar">
615
+ <div class="flex-1">Ready</div>
616
+ <div class="flex items-center space-x-4">
617
+ <span>Zoom: 100%</span>
618
+ <span>Selected: 1 node</span>
619
+ <span>Total: 0 nodes</span>
620
+ </div>
621
+ </footer>
622
+ </div>
623
+
624
+ <!-- API Settings Modal -->
625
+ <div id="apiSettingsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
626
+ <div class="bg-slate-800 rounded-xl max-w-md w-full p-6 border border-slate-700">
627
+ <div class="flex justify-between items-center mb-4">
628
+ <h3 class="text-lg font-medium">API Settings</h3>
629
+ <button id="closeApiSettingsBtn" class="p-2 rounded-full hover:bg-slate-700">
630
+ <i class="fas fa-times"></i>
631
+ </button>
632
+ </div>
633
+
634
+ <div class="space-y-4">
635
+ <div>
636
+ <label class="block text-sm font-medium mb-1">OpenAI API Key</label>
637
+ <div class="flex space-x-2">
638
+ <input type="password" id="apiKeyInput" placeholder="sk-...your-api-key" class="flex-1 bg-slate-700 border border-slate-600 rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
639
+ <button id="toggleApiKeyVisibility" class="p-2 rounded-lg bg-slate-700 hover:bg-slate-600">
640
+ <i class="fas fa-eye"></i>
641
+ </button>
642
+ </div>
643
+ <p class="text-xs text-slate-400 mt-1">Manage your API key at <a href="https://platform.openai.com/account/api-keys" target="_blank" class="text-blue-400 hover:underline">OpenAI</a></p>
644
+ </div>
645
+
646
+ <div class="pt-4 border-t border-slate-700 flex justify-end">
647
+ <button id="saveApiSettingsBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-sm font-medium transition-colors">
648
+ Save Settings
649
+ </button>
650
+ </div>
651
+ </div>
652
+ </div>
653
+ </div>
654
+
655
+ <!-- Context Menu -->
656
+ <div id="contextMenu" class="context-menu hidden">
657
+ <div class="context-menu-item" data-action="delete">
658
+ <i class="fas fa-trash text-red-400"></i>
659
+ <span>Delete</span>
660
+ </div>
661
+ <div class="context-menu-item" data-action="duplicate">
662
+ <i class="fas fa-copy text-blue-400"></i>
663
+ <span>Duplicate</span>
664
+ </div>
665
+ <div class="context-menu-item" data-action="copy">
666
+ <i class="fas fa-clipboard text-green-400"></i>
667
+ <span>Copy</span>
668
+ </div>
669
+ <div class="context-menu-item" data-action="paste">
670
+ <i class="fas fa-paste text-yellow-400"></i>
671
+ <span>Paste</span>
672
+ </div>
673
+ </div>
674
+
675
+ <!-- Tooltip -->
676
+ <div id="tooltip" class="tooltip hidden"></div>
677
+
678
+ <!-- Connection being dragged -->
679
+ <svg id="draggingConnection" class="dragging-connection hidden" width="100%" height="100%">
680
+ <path class="dragging-connection-path"></path>
681
+ </svg>
682
+
683
+ <!-- All connections -->
684
+ <svg id="connections" class="connection" width="100%" height="100%">
685
+ <!-- Connections will be added here dynamically -->
686
+ </svg>
687
+
688
+ <script>
689
+ document.addEventListener('DOMContentLoaded', function() {
690
+ // DOM Elements
691
+ const canvas = document.getElementById('canvas');
692
+ const canvasContainer = document.getElementById('canvasContainer');
693
+ const paletteItems = document.querySelectorAll('.palette-item');
694
+ const apiSettingsBtn = document.getElementById('apiSettingsBtn');
695
+ const closeApiSettingsBtn = document.getElementById('closeApiSettingsBtn');
696
+ const apiSettingsModal = document.getElementById('apiSettingsModal');
697
+ const apiKeyInput = document.getElementById('apiKeyInput');
698
+ const toggleApiKeyVisibility = document.getElementById('toggleApiKeyVisibility');
699
+ const saveApiSettingsBtn = document.getElementById('saveApiSettingsBtn');
700
+ const runWorkflowBtn = document.getElementById('runWorkflowBtn');
701
+ const saveWorkflowBtn = document.getElementById('saveWorkflowBtn');
702
+ const tabs = document.querySelectorAll('.tab');
703
+ const tabContents = document.querySelectorAll('.tab-content');
704
+ const contextMenu = document.getElementById('contextMenu');
705
+ const tooltip = document.getElementById('tooltip');
706
+ const zoomInBtn = document.getElementById('zoomInBtn');
707
+ const zoomOutBtn = document.getElementById('zoomOutBtn');
708
+ const zoomResetBtn = document.getElementById('zoomResetBtn');
709
+ const draggingConnection = document.getElementById('draggingConnection');
710
+ const connections = document.getElementById('connections');
711
+
712
+ // State
713
+ let selectedNode = null;
714
+ let nodes = [];
715
+ let connectionsList = [];
716
+ let isDragging = false;
717
+ let startX, startY;
718
+ let offsetX = 0, offsetY = 0;
719
+ let scale = 1;
720
+ let isDraggingConnection = false;
721
+ let connectionStart = null;
722
+ let copiedNode = null;
723
+ let apiKey = localStorage.getItem('novaFlow_apiKey') || '';
724
+
725
+ // Initialize
726
+ if (apiKey) apiKeyInput.value = apiKey;
727
+
728
+ // Event Listeners
729
+ paletteItems.forEach(item => {
730
+ item.addEventListener('dragstart', handleDragStart);
731
+ });
732
+
733
+ canvas.addEventListener('dragover', handleDragOver);
734
+ canvas.addEventListener('drop', handleDrop);
735
+ canvas.addEventListener('mousedown', handleCanvasMouseDown);
736
+ canvas.addEventListener('mousemove', handleCanvasMouseMove);
737
+ canvas.addEventListener('mouseup', handleCanvasMouseUp);
738
+ canvas.addEventListener('wheel', handleCanvasWheel, { passive: false });
739
+ canvas.addEventListener('contextmenu', handleContextMenu);
740
+
741
+ apiSettingsBtn.addEventListener('click', () => apiSettingsModal.classList.remove('hidden'));
742
+ closeApiSettingsBtn.addEventListener('click', () => apiSettingsModal.classList.add('hidden'));
743
+ saveApiSettingsBtn.addEventListener('click', saveApiSettings);
744
+ toggleApiKeyVisibility.addEventListener('click', toggleApiKeyVisibilityHandler);
745
+ runWorkflowBtn.addEventListener('click', runWorkflow);
746
+ saveWorkflowBtn.addEventListener('click', saveWorkflow);
747
+
748
+ tabs.forEach(tab => {
749
+ tab.addEventListener('click', switchTab);
750
+ });
751
+
752
+ zoomInBtn.addEventListener('click', () => zoomCanvas(1.2));
753
+ zoomOutBtn.addEventListener('click', () => zoomCanvas(0.8));
754
+ zoomResetBtn.addEventListener('click', () => resetZoom());
755
+
756
+ document.addEventListener('click', closeContextMenu);
757
+ document.addEventListener('keydown', handleKeyDown);
758
+
759
+ // Functions
760
+ function handleDragStart(e) {
761
+ e.dataTransfer.setData('text/plain', e.target.dataset.type);
762
+ e.dataTransfer.effectAllowed = 'copy';
763
+ }
764
+
765
+ function handleDragOver(e) {
766
+ e.preventDefault();
767
+ e.dataTransfer.dropEffect = 'copy';
768
+ }
769
+
770
+ function handleDrop(e) {
771
+ e.preventDefault();
772
+ const nodeType = e.dataTransfer.getData('text/plain');
773
+ if (!nodeType) return;
774
+
775
+ const rect = canvas.getBoundingClientRect();
776
+ const x = (e.clientX - rect.left - offsetX) / scale;
777
+ const y = (e.clientY - rect.top - offsetY) / scale;
778
+
779
+ createNode(nodeType, x, y);
780
+ }
781
+
782
+ function createNode(type, x, y) {
783
+ const nodeId = 'node-' + Date.now();
784
+ const nodeColors = {
785
+ 'llm': { bg: 'bg-blue-900', text: 'text-blue-300', icon: 'fa-robot' },
786
+ 'code': { bg: 'bg-purple-900', text: 'text-purple-300', icon: 'fa-code' },
787
+ 'decision': { bg: 'bg-green-900', text: 'text-green-300', icon: 'fa-code-branch' },
788
+ 'transform': { bg: 'bg-yellow-900', text: 'text-yellow-300', icon: 'fa-sliders-h' },
789
+ 'filter': { bg: 'bg-red-900', text: 'text-red-300', icon: 'fa-filter' },
790
+ 'join': { bg: 'bg-indigo-900', text: 'text-indigo-300', icon: 'fa-link' },
791
+ 'api': { bg: 'bg-pink-900', text: 'text-pink-300', icon: 'fa-cloud' },
792
+ 'webhook': { bg: 'bg-teal-900', text: 'text-teal-300', icon: 'fa-bell' },
793
+ 'database': { bg: 'bg-amber-900', text: 'text-amber-300', icon: 'fa-database' }
794
+ };
795
+
796
+ const nodeTitle = {
797
+ 'llm': 'LLM Prompt',
798
+ 'code': 'Code Execution',
799
+ 'decision': 'Decision',
800
+ 'transform': 'Data Transform',
801
+ 'filter': 'Filter',
802
+ 'join': 'Join Data',
803
+ 'api': 'API Call',
804
+ 'webhook': 'Webhook',
805
+ 'database': 'Database Query'
806
+ };
807
+
808
+ const node = document.createElement('div');
809
+ node.className = `node ${type === 'decision' ? 'w-48' : 'w-64'}`;
810
+ node.id = nodeId;
811
+ node.style.left = `${x}px`;
812
+ node.style.top = `${y}px`;
813
+ node.dataset.type = type;
814
+
815
+ node.innerHTML = `
816
+ <div class="node-header ${nodeColors[type].text}">
817
+ <div class="flex items-center">
818
+ <div class="node-icon ${nodeColors[type].bg} ${nodeColors[type].text}">
819
+ <i class="fas ${nodeColors[type].icon}"></i>
820
+ </div>
821
+ <div class="node-title">${nodeTitle[type]}</div>
822
+ </div>
823
+ <div class="node-actions">
824
+ <div class="node-action-btn" data-action="settings">
825
+ <i class="fas fa-cog"></i>
826
+ </div>
827
+ </div>
828
+ </div>
829
+ <div class="node-content">
830
+ ${type === 'llm' ? `
831
+ <div class="text-xs text-slate-400 mb-2">Prompt node</div>
832
+ <div class="text-xs truncate">Click to configure...</div>
833
+ ` : ''}
834
+ ${type === 'code' ? `
835
+ <div class="text-xs text-slate-400 mb-2">Code execution</div>
836
+ <div class="text-xs truncate">Python, JavaScript, etc.</div>
837
+ ` : ''}
838
+ ${type === 'decision' ? `
839
+ <div class="text-xs text-slate-400 mb-2">Decision node</div>
840
+ <div class="text-xs truncate">If-else conditions</div>
841
+ ` : ''}
842
+ ${type === 'api' ? `
843
+ <div class="text-xs text-slate-400 mb-2">API call</div>
844
+ <div class="text-xs truncate">GET, POST, etc.</div>
845
+ ` : ''}
846
+ </div>
847
+ <div class="connector input" data-node="${nodeId}" data-type="input"></div>
848
+ <div class="connector output" data-node="${nodeId}" data-type="output"></div>
849
+ `;
850
+
851
+ canvas.appendChild(node);
852
+
853
+ // Add to nodes array
854
+ nodes.push({
855
+ id: nodeId,
856
+ type: type,
857
+ x: x,
858
+ y: y,
859
+ properties: {
860
+ name: nodeTitle[type],
861
+ description: '',
862
+ inputs: [],
863
+ outputs: []
864
+ }
865
+ });
866
+
867
+ // Add event listeners to the node
868
+ node.addEventListener('mousedown', handleNodeMouseDown);
869
+ node.querySelector('.node-header').addEventListener('dblclick', handleNodeDoubleClick);
870
+
871
+ // Add event listeners to connectors
872
+ const inputConnector = node.querySelector('.connector.input');
873
+ const outputConnector = node.querySelector('.connector.output');
874
+
875
+ inputConnector.addEventListener('mousedown', startConnectionDrag);
876
+ outputConnector.addEventListener('mousedown', startConnectionDrag);
877
+
878
+ // Add event listeners to action buttons
879
+ const actionButtons = node.querySelectorAll('.node-action-btn');
880
+ actionButtons.forEach(btn => {
881
+ btn.addEventListener('click', function(e) {
882
+ e.stopPropagation();
883
+ const action = this.dataset.action;
884
+ if (action === 'settings') {
885
+ selectNode(node);
886
+ }
887
+ });
888
+ });
889
+
890
+ return node;
891
+ }
892
+
893
+ function handleNodeMouseDown(e) {
894
+ if (e.button !== 0) return; // Only left click
895
+
896
+ e.stopPropagation();
897
+ isDragging = true;
898
+ startX = e.clientX;
899
+ startY = e.clientY;
900
+
901
+ const node = e.currentTarget;
902
+ selectNode(node);
903
+
904
+ node.style.cursor = 'grabbing';
905
+ node.style.zIndex = 10;
906
+
907
+ document.addEventListener('mousemove', handleNodeDrag);
908
+ document.addEventListener('mouseup', stopNodeDrag);
909
+ }
910
+
911
+ function handleNodeDoubleClick(e) {
912
+ e.stopPropagation();
913
+ const node = e.currentTarget.closest('.node');
914
+ selectNode(node);
915
+
916
+ // Focus on the node name input
917
+ document.getElementById('nodeNameInput').focus();
918
+ }
919
+
920
+ function handleNodeDrag(e) {
921
+ if (!isDragging) return;
922
+
923
+ const node = selectedNode;
924
+ if (!node) return;
925
+
926
+ const dx = (e.clientX - startX) / scale;
927
+ const dy = (e.clientY - startY) / scale;
928
+
929
+ const currentX = parseFloat(node.style.left) || 0;
930
+ const currentY = parseFloat(node.style.top) || 0;
931
+
932
+ node.style.left = `${currentX + dx}px`;
933
+ node.style.top = `${currentY + dy}px`;
934
+
935
+ // Update node position in nodes array
936
+ const nodeData = nodes.find(n => n.id === node.id);
937
+ if (nodeData) {
938
+ nodeData.x = currentX + dx;
939
+ nodeData.y = currentY + dy;
940
+ }
941
+
942
+ // Update connections
943
+ updateConnections();
944
+
945
+ startX = e.clientX;
946
+ startY = e.clientY;
947
+ }
948
+
949
+ function stopNodeDrag() {
950
+ isDragging = false;
951
+ if (selectedNode) {
952
+ selectedNode.style.cursor = 'grab';
953
+ selectedNode.style.zIndex = '';
954
+ }
955
+
956
+ document.removeEventListener('mousemove', handleNodeDrag);
957
+ document.removeEventListener('mouseup', stopNodeDrag);
958
+ }
959
+
960
+ function selectNode(node) {
961
+ // Deselect current node
962
+ if (selectedNode) {
963
+ selectedNode.classList.remove('selected');
964
+ }
965
+
966
+ // Select new node
967
+ selectedNode = node;
968
+ node.classList.add('selected');
969
+
970
+ // Update property editor
971
+ const nodeData = nodes.find(n => n.id === node.id);
972
+ if (nodeData) {
973
+ document.getElementById('nodeNameInput').value = nodeData.properties.name || '';
974
+ document.getElementById('nodeTypeInput').value = nodeData.type;
975
+
976
+ // Update other properties based on node type
977
+ if (nodeData.type === 'llm') {
978
+ document.getElementById('modelSelect').value = nodeData.properties.model || 'gpt-3.5-turbo';
979
+ document.getElementById('temperatureSlider').value = nodeData.properties.temperature || 0.7;
980
+ document.getElementById('systemPromptInput').value = nodeData.properties.systemPrompt || '';
981
+ document.getElementById('userPromptInput').value = nodeData.properties.userPrompt || '';
982
+ document.getElementById('inputVarsInput').value = nodeData.properties.inputVars || '';
983
+ document.getElementById('outputVarInput').value = nodeData.properties.outputVar || '';
984
+ }
985
+ }
986
+ }
987
+
988
+ function startConnectionDrag(e) {
989
+ e.stopPropagation();
990
+ isDraggingConnection = true;
991
+ connectionStart = {
992
+ node: e.currentTarget.closest('.node').id,
993
+ type: e.currentTarget.dataset.type,
994
+ x: e.currentTarget.getBoundingClientRect().left + e.currentTarget.offsetWidth / 2,
995
+ y: e.currentTarget.getBoundingClientRect().top + e.currentTarget.offsetHeight / 2
996
+ };
997
+
998
+ draggingConnection.classList.remove('hidden');
999
+
1000
+ document.addEventListener('mousemove', updateConnectionDrag);
1001
+ document.addEventListener('mouseup', completeConnectionDrag);
1002
+ }
1003
+
1004
+ function updateConnectionDrag(e) {
1005
+ if (!isDraggingConnection) return;
1006
+
1007
+ const path = draggingConnection.querySelector('.dragging-connection-path');
1008
+ const startX = connectionStart.x;
1009
+ const startY = connectionStart.y;
1010
+ const endX = e.clientX;
1011
+ const endY = e.clientY;
1012
+
1013
+ // Create a smooth bezier curve between the points
1014
+ const midX = (startX + endX) / 2;
1015
+ const pathData = `M${startX},${startY} C${midX},${startY} ${midX},${endY} ${endX},${endY}`;
1016
+
1017
+ path.setAttribute('d', pathData);
1018
+ }
1019
+
1020
+ function completeConnectionDrag(e) {
1021
+ if (!isDraggingConnection) return;
1022
+
1023
+ draggingConnection.classList.add('hidden');
1024
+ isDraggingConnection = false;
1025
+
1026
+ // Check if we're connecting to another connector
1027
+ const element = document.elementFromPoint(e.clientX, e.clientY);
1028
+ if (element && element.classList.contains('connector')) {
1029
+ const targetNode = element.closest('.node');
1030
+ const targetType = element.dataset.type;
1031
+
1032
+ // Validate connection (input to output or vice versa)
1033
+ if ((connectionStart.type === 'output' && targetType === 'input') ||
1034
+ (connectionStart.type === 'input' && targetType === 'output')) {
1035
+
1036
+ // Don't allow connections to the same node
1037
+ if (connectionStart.node !== targetNode.id) {
1038
+ createConnection(
1039
+ connectionStart.node,
1040
+ connectionStart.type,
1041
+ targetNode.id,
1042
+ targetType
1043
+ );
1044
+ }
1045
+ }
1046
+ }
1047
+
1048
+ document.removeEventListener('mousemove', updateConnectionDrag);
1049
+ document.removeEventListener('mouseup', completeConnectionDrag);
1050
+ }
1051
+
1052
+ function createConnection(sourceNodeId, sourceType, targetNodeId, targetType) {
1053
+ // Determine which is the output and which is the input
1054
+ let fromNode, fromType, toNode, toType;
1055
+
1056
+ if (sourceType === 'output') {
1057
+ fromNode = sourceNodeId;
1058
+ fromType = 'output';
1059
+ toNode = targetNodeId;
1060
+ toType = 'input';
1061
+ } else {
1062
+ fromNode = targetNodeId;
1063
+ fromType = 'output';
1064
+ toNode = sourceNodeId;
1065
+ toType = 'input';
1066
+ }
1067
+
1068
+ // Check if connection already exists
1069
+ const exists = connectionsList.some(conn =>
1070
+ conn.fromNode === fromNode && conn.toNode === toNode
1071
+ );
1072
+
1073
+ if (exists) return;
1074
+
1075
+ // Add to connections list
1076
+ connectionsList.push({
1077
+ id: `conn-${Date.now()}`,
1078
+ fromNode: fromNode,
1079
+ fromType: fromType,
1080
+ toNode: toNode,
1081
+ toType: toType
1082
+ });
1083
+
1084
+ // Update connections display
1085
+ updateConnections();
1086
+ }
1087
+
1088
+ function updateConnections() {
1089
+ // Clear existing connections
1090
+ connections.innerHTML = '';
1091
+
1092
+ // Draw all connections
1093
+ connectionsList.forEach(conn => {
1094
+ const fromNode = document.getElementById(conn.fromNode);
1095
+ const toNode = document.getElementById(conn.toNode);
1096
+
1097
+ if (!fromNode || !toNode) return;
1098
+
1099
+ const fromConnector = fromNode.querySelector(`.connector.${conn.fromType}`);
1100
+ const toConnector = toNode.querySelector(`.connector.${conn.toType}`);
1101
+
1102
+ if (!fromConnector || !toConnector) return;
1103
+
1104
+ const fromRect = fromConnector.getBoundingClientRect();
1105
+ const toRect = toConnector.getBoundingClientRect();
1106
+
1107
+ const fromX = fromRect.left + fromRect.width / 2;
1108
+ const fromY = fromRect.top + fromRect.height / 2;
1109
+ const toX = toRect.left + toRect.width / 2;
1110
+ const toY = toRect.top + toRect.height / 2;
1111
+
1112
+ const midX = (fromX + toX) / 2;
1113
+
1114
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1115
+ path.classList.add('connection-path');
1116
+ path.setAttribute('d', `M${fromX},${fromY} C${midX},${fromY} ${midX},${toY} ${toX},${toY}`);
1117
+
1118
+ // Highlight if connected to selected node
1119
+ if (selectedNode && (conn.fromNode === selectedNode.id || conn.toNode === selectedNode.id)) {
1120
+ path.classList.add('active');
1121
+ }
1122
+
1123
+ connections.appendChild(path);
1124
+ });
1125
+ }
1126
+
1127
+ function handleCanvasMouseDown(e) {
1128
+ if (e.button !== 0) return; // Only left click
1129
+ if (e.target !== canvas) return;
1130
+
1131
+ isDragging = true;
1132
+ startX = e.clientX;
1133
+ startY = e.clientY;
1134
+
1135
+ // Deselect any selected node
1136
+ if (selectedNode) {
1137
+ selectedNode.classList.remove('selected');
1138
+ selectedNode = null;
1139
+ }
1140
+
1141
+ document.addEventListener('mousemove', handleCanvasDrag);
1142
+ document.addEventListener('mouseup', stopCanvasDrag);
1143
+ }
1144
+
1145
+ function handleCanvasDrag(e) {
1146
+ if (!isDragging) return;
1147
+
1148
+ const dx = e.clientX - startX;
1149
+ const dy = e.clientY - startY;
1150
+
1151
+ offsetX += dx;
1152
+ offsetY += dy;
1153
+
1154
+ canvas.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
1155
+
1156
+ startX = e.clientX;
1157
+ startY = e.clientY;
1158
+ }
1159
+
1160
+ function stopCanvasDrag() {
1161
+ isDragging = false;
1162
+ document.removeEventListener('mousemove', handleCanvasDrag);
1163
+ document.removeEventListener('mouseup', stopCanvasDrag);
1164
+ }
1165
+
1166
+ function handleCanvasMouseMove(e) {
1167
+ // Update tooltip position
1168
+ tooltip.style.left = `${e.clientX + 10}px`;
1169
+ tooltip.style.top = `${e.clientY + 10}px`;
1170
+
1171
+ // Show connector tooltips
1172
+ const element = document.elementFromPoint(e.clientX, e.clientY);
1173
+ if (element && element.classList.contains('connector')) {
1174
+ const node = element.closest('.node');
1175
+ tooltip.textContent = `${element.dataset.type} connector`;
1176
+ tooltip.classList.remove('hidden');
1177
+ } else {
1178
+ tooltip.classList.add('hidden');
1179
+ }
1180
+ }
1181
+
1182
+ function handleCanvasMouseUp(e) {
1183
+ // Handle click (not drag)
1184
+ if (!isDragging && e.button === 0 && e.target === canvas) {
1185
+ // Deselect any selected node
1186
+ if (selectedNode) {
1187
+ selectedNode.classList.remove('selected');
1188
+ selectedNode = null;
1189
+ }
1190
+ }
1191
+ }
1192
+
1193
+ function handleCanvasWheel(e) {
1194
+ e.preventDefault();
1195
+
1196
+ // Zoom in/out with mouse wheel
1197
+ const delta = -e.deltaY;
1198
+ const zoomFactor = delta > 0 ? 1.1 : 0.9;
1199
+
1200
+ zoomCanvas(zoomFactor, e.clientX, e.clientY);
1201
+ }
1202
+
1203
+ function zoomCanvas(zoomFactor, centerX, centerY) {
1204
+ const oldScale = scale;
1205
+ scale *= zoomFactor;
1206
+
1207
+ // Limit zoom
1208
+ scale = Math.min(Math.max(0.5, scale), 3);
1209
+
1210
+ if (scale === oldScale) return;
1211
+
1212
+ // Calculate new offset to zoom toward mouse position
1213
+ if (centerX !== undefined && centerY !== undefined) {
1214
+ const rect = canvasContainer.getBoundingClientRect();
1215
+ const mouseX = centerX - rect.left;
1216
+ const mouseY = centerY - rect.top;
1217
+
1218
+ offsetX = mouseX - (mouseX - offsetX) * (scale / oldScale);
1219
+ offsetY = mouseY - (mouseY - offsetY) * (scale / oldScale);
1220
+ }
1221
+
1222
+ // Apply transform
1223
+ canvas.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
1224
+
1225
+ // Update status bar
1226
+ document.querySelector('.status-bar span:nth-child(2)').textContent = `Zoom: ${Math.round(scale * 100)}%`;
1227
+ }
1228
+
1229
+ function resetZoom() {
1230
+ scale = 1;
1231
+ offsetX = 0;
1232
+ offsetY = 0;
1233
+ canvas.style.transform = 'translate(0, 0) scale(1)';
1234
+ document.querySelector('.status-bar span:nth-child(2)').textContent = 'Zoom: 100%';
1235
+ }
1236
+
1237
+ function handleContextMenu(e) {
1238
+ e.preventDefault();
1239
+
1240
+ // Close any existing context menu
1241
+ closeContextMenu();
1242
+
1243
+ // Show context menu at mouse position
1244
+ contextMenu.style.left = `${e.clientX}px`;
1245
+ contextMenu.style.top = `${e.clientY}px`;
1246
+ contextMenu.classList.remove('hidden');
1247
+
1248
+ // Store position for paste operation
1249
+ contextMenu.dataset.x = e.clientX;
1250
+ contextMenu.dataset.y = e.clientY;
1251
+
1252
+ return false;
1253
+ }
1254
+
1255
+ function closeContextMenu(e) {
1256
+ if (e && e.target.closest('.context-menu')) return;
1257
+ contextMenu.classList.add('hidden');
1258
+ }
1259
+
1260
+ function handleKeyDown(e) {
1261
+ // Delete selected node
1262
+ if (e.key === 'Delete' && selectedNode) {
1263
+ deleteNode(selectedNode);
1264
+ }
1265
+
1266
+ // Copy selected node
1267
+ if (e.ctrlKey && e.key === 'c' && selectedNode) {
1268
+ copyNode(selectedNode);
1269
+ }
1270
+
1271
+ // Paste copied node
1272
+ if (e.ctrlKey && e.key === 'v' && copiedNode) {
1273
+ pasteNode();
1274
+ }
1275
+ }
1276
+
1277
+ function deleteNode(node) {
1278
+ // Remove from nodes array
1279
+ nodes = nodes.filter(n => n.id !== node.id);
1280
+
1281
+ // Remove connections involving this node
1282
+ connectionsList = connectionsList.filter(conn =>
1283
+ conn.fromNode !== node.id && conn.toNode !== node.id
1284
+ );
1285
+
1286
+ // Remove from DOM
1287
+ node.remove();
1288
+
1289
+ // Update connections
1290
+ updateConnections();
1291
+
1292
+ // Clear selection
1293
+ selectedNode = null;
1294
+
1295
+ // Update status bar
1296
+ document.querySelector('.status-bar span:nth-child(3)').textContent = `Total: ${nodes.length} nodes`;
1297
+ }
1298
+
1299
+ function copyNode(node) {
1300
+ const nodeData = nodes.find(n => n.id === node.id);
1301
+ if (nodeData) {
1302
+ copiedNode = JSON.parse(JSON.stringify(nodeData));
1303
+ }
1304
+ }
1305
+
1306
+ function pasteNode() {
1307
+ if (!copiedNode) return;
1308
+
1309
+ const rect = canvasContainer.getBoundingClientRect();
1310
+ const x = (parseInt(contextMenu.dataset.x) - rect.left - offsetX) / scale;
1311
+ const y = (parseInt(contextMenu.dataset.y) - rect.top - offsetY) / scale;
1312
+
1313
+ // Create new node with similar properties
1314
+ const newNode = createNode(copiedNode.type, x, y);
1315
+ const newNodeData = nodes.find(n => n.id === newNode.id);
1316
+
1317
+ if (newNodeData) {
1318
+ // Copy properties
1319
+ newNodeData.properties = JSON.parse(JSON.stringify(copiedNode.properties));
1320
+
1321
+ // Update node display
1322
+ selectNode(newNode);
1323
+ }
1324
+ }
1325
+
1326
+ function switchTab(e) {
1327
+ const tab = e.currentTarget;
1328
+ const tabName = tab.dataset.tab;
1329
+
1330
+ // Update active tab
1331
+ tabs.forEach(t => t.classList.remove('active'));
1332
+ tab.classList.add('active');
1333
+
1334
+ // Show corresponding content
1335
+ tabContents.forEach(content => {
1336
+ content.classList.add('hidden');
1337
+ if (content.id === `${tabName}Tab`) {
1338
+ content.classList.remove('hidden');
1339
+ }
1340
+ });
1341
+ }
1342
+
1343
+ function saveApiSettings() {
1344
+ const key = apiKeyInput.value.trim();
1345
+ if (!key) {
1346
+ alert('Please enter your API key');
1347
+ return;
1348
+ }
1349
+
1350
+ if (!key.startsWith('sk-')) {
1351
+ alert('API keys typically start with "sk-". Please check your key.');
1352
+ return;
1353
+ }
1354
+
1355
+ localStorage.setItem('novaFlow_apiKey', key);
1356
+ apiKey = key;
1357
+ apiSettingsModal.classList.add('hidden');
1358
+
1359
+ // Enable run button
1360
+ runWorkflowBtn.disabled = false;
1361
+ }
1362
+
1363
+ function toggleApiKeyVisibilityHandler() {
1364
+ const type = apiKeyInput.getAttribute('type') === 'password' ? 'text' : 'password';
1365
+ apiKeyInput.setAttribute('type', type);
1366
+ this.innerHTML = type === 'password' ? '<i class="fas fa-eye"></i>' : '<i class="fas fa-eye-slash"></i>';
1367
+ }
1368
+
1369
+ function runWorkflow() {
1370
+ if (!apiKey) {
1371
+ alert('Please set your OpenAI API key first');
1372
+ apiSettingsModal.classList.remove('hidden');
1373
+ return;
1374
+ }
1375
+
1376
+ if (nodes.length === 0) {
1377
+ alert('Your workflow is empty. Add some nodes first.');
1378
+ return;
1379
+ }
1380
+
1381
+ alert('Workflow execution started! (This is a demo - in a real app, this would execute your workflow)');
1382
+
1383
+ // In a real implementation, you would:
1384
+ // 1. Validate the workflow (check for cycles, required inputs, etc.)
1385
+ // 2. Topologically sort the nodes
1386
+ // 3. Execute each node in order, passing outputs to connected inputs
1387
+ // 4. Handle errors and display results
1388
+ }
1389
+
1390
+ function saveWorkflow() {
1391
+ if (nodes.length === 0) {
1392
+ alert('Your workflow is empty. Add some nodes first.');
1393
+ return;
1394
+ }
1395
+
1396
+ // Prepare workflow data
1397
+ const workflowData = {
1398
+ name: document.getElementById('workflowNameInput').value,
1399
+ description: document.getElementById('workflowDescInput').value,
1400
+ nodes: nodes,
1401
+ connections: connectionsList,
1402
+ variables: JSON.parse(document.getElementById('variablesEditor').value),
1403
+ createdAt: new Date().toISOString()
1404
+ };
1405
+
1406
+ // Update JSON editor
1407
+ document.getElementById('workflowEditor').value = JSON.stringify(workflowData, null, 2);
1408
+
1409
+ // In a real implementation, you would save to a database or localStorage
1410
+ localStorage.setItem('novaFlow_workflow', JSON.stringify(workflowData));
1411
+
1412
+ alert('Workflow saved! (This is a demo - in a real app, this would save to your account)');
1413
+ }
1414
+
1415
+ // Initialize with a sample node
1416
+ setTimeout(() => {
1417
+ const sampleNode = createNode('llm', 200, 100);
1418
+ selectNode(sampleNode);
1419
+
1420
+ // Update status bar
1421
+ document.querySelector('.status-bar span:nth-child(3)').textContent = `Total: ${nodes.length} nodes`;
1422
+ }, 100);
1423
+ });
1424
+ </script>
1425
+ <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=LukasBe/visual-agentic-editor" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1426
+ </html>
prompts.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Create sleek, gen-z attractive, chatgpt environment, with a tab to a guided comfortable setup OpenAI API connection via api key, and use onlny client side JS api calls for the entire app.
2
+ Turn it to a visual agentic editor able to connect multiple steps to form a workflow