theaimoron commited on
Commit
fc9f7e9
·
verified ·
1 Parent(s): 3fbacfb

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +6 -4
  2. index.html +1376 -19
  3. prompts.txt +2 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: My Attempt At Automation
3
- emoji:
4
- colorFrom: gray
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: my-attempt-at-automation
3
+ emoji: 🐳
4
+ colorFrom: yellow
5
  colorTo: blue
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,1376 @@
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>FlowForge - Visual Workflow Automation</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
+ .node {
11
+ cursor: grab;
12
+ transition: all 0.2s ease;
13
+ }
14
+ .node:hover {
15
+ transform: translateY(-2px);
16
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
17
+ }
18
+ .node.selected {
19
+ border-color: #3b82f6;
20
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
21
+ }
22
+ .connection-path {
23
+ stroke: #94a3b8;
24
+ stroke-width: 2;
25
+ fill: none;
26
+ }
27
+ .workflow-canvas {
28
+ background-color: #f8fafc;
29
+ background-image:
30
+ linear-gradient(to right, #e2e8f0 1px, transparent 1px),
31
+ linear-gradient(to bottom, #e2e8f0 1px, transparent 1px);
32
+ background-size: 20px 20px;
33
+ }
34
+ .resize-handle {
35
+ width: 10px;
36
+ height: 10px;
37
+ background-color: #3b82f6;
38
+ position: absolute;
39
+ right: 0;
40
+ bottom: 0;
41
+ cursor: nwse-resize;
42
+ border-radius: 2px;
43
+ }
44
+ .node-port {
45
+ width: 12px;
46
+ height: 12px;
47
+ border-radius: 50%;
48
+ background-color: #64748b;
49
+ cursor: pointer;
50
+ }
51
+ .node-port.input {
52
+ left: -6px;
53
+ }
54
+ .node-port.output {
55
+ right: -6px;
56
+ }
57
+ .sidebar {
58
+ scrollbar-width: thin;
59
+ scrollbar-color: #cbd5e1 #f1f5f9;
60
+ }
61
+ .sidebar::-webkit-scrollbar {
62
+ width: 6px;
63
+ }
64
+ .sidebar::-webkit-scrollbar-track {
65
+ background: #f1f5f9;
66
+ }
67
+ .sidebar::-webkit-scrollbar-thumb {
68
+ background-color: #cbd5e1;
69
+ border-radius: 3px;
70
+ }
71
+ .tutorial-overlay {
72
+ position: fixed;
73
+ top: 0;
74
+ left: 0;
75
+ right: 0;
76
+ bottom: 0;
77
+ background-color: rgba(0, 0, 0, 0.7);
78
+ z-index: 100;
79
+ display: flex;
80
+ justify-content: center;
81
+ align-items: center;
82
+ }
83
+ .tutorial-card {
84
+ background-color: white;
85
+ border-radius: 0.5rem;
86
+ width: 90%;
87
+ max-width: 600px;
88
+ max-height: 90vh;
89
+ overflow-y: auto;
90
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
91
+ }
92
+ .tutorial-highlight {
93
+ position: absolute;
94
+ border-radius: 0.5rem;
95
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
96
+ z-index: 101;
97
+ pointer-events: none;
98
+ }
99
+ .tutorial-tooltip {
100
+ position: absolute;
101
+ background-color: white;
102
+ padding: 0.5rem 1rem;
103
+ border-radius: 0.5rem;
104
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
105
+ z-index: 102;
106
+ max-width: 300px;
107
+ }
108
+ .animate-pulse {
109
+ animation: pulse 2s infinite;
110
+ }
111
+ @keyframes pulse {
112
+ 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); }
113
+ 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); }
114
+ 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
115
+ }
116
+ </style>
117
+ </head>
118
+ <body class="bg-gray-50 h-screen flex flex-col overflow-hidden">
119
+ <!-- Header -->
120
+ <header class="bg-white border-b border-gray-200 px-4 py-3 flex items-center justify-between">
121
+ <div class="flex items-center space-x-2">
122
+ <div class="bg-blue-500 text-white p-2 rounded-lg">
123
+ <i class="fas fa-project-diagram text-lg"></i>
124
+ </div>
125
+ <h1 class="text-xl font-bold text-gray-800">FlowForge</h1>
126
+ </div>
127
+ <div class="flex items-center space-x-4">
128
+ <button id="start-tutorial" class="px-4 py-2 bg-purple-500 text-white rounded-md hover:bg-purple-600 transition flex items-center space-x-2">
129
+ <i class="fas fa-graduation-cap"></i>
130
+ <span>Start Tutorial</span>
131
+ </button>
132
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition flex items-center space-x-2">
133
+ <i class="fas fa-play"></i>
134
+ <span>Execute</span>
135
+ </button>
136
+ <button class="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition flex items-center space-x-2">
137
+ <i class="fas fa-save"></i>
138
+ <span>Save</span>
139
+ </button>
140
+ <div class="relative">
141
+ <button class="p-2 rounded-full hover:bg-gray-100">
142
+ <i class="fas fa-user-circle text-xl text-gray-600"></i>
143
+ </button>
144
+ </div>
145
+ </div>
146
+ </header>
147
+
148
+ <!-- Main Content -->
149
+ <div class="flex flex-1 overflow-hidden">
150
+ <!-- Left Sidebar - Nodes -->
151
+ <div class="w-64 bg-white border-r border-gray-200 flex flex-col sidebar overflow-y-auto">
152
+ <div class="p-4 border-b border-gray-200">
153
+ <div class="relative">
154
+ <input type="text" placeholder="Search nodes..." class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
155
+ <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
156
+ </div>
157
+ </div>
158
+ <div class="p-4">
159
+ <h3 class="font-medium text-gray-700 mb-2">Triggers</h3>
160
+ <div class="space-y-2">
161
+ <div class="node-item bg-white border border-gray-200 rounded-md p-3 cursor-pointer hover:bg-blue-50" data-type="trigger" data-node="webhook">
162
+ <div class="flex items-center space-x-3">
163
+ <div class="bg-red-100 p-2 rounded-md">
164
+ <i class="fas fa-bolt text-red-500"></i>
165
+ </div>
166
+ <div>
167
+ <h4 class="font-medium">Webhook</h4>
168
+ <p class="text-xs text-gray-500">Trigger workflow with HTTP request</p>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ <div class="node-item bg-white border border-gray-200 rounded-md p-3 cursor-pointer hover:bg-blue-50" data-type="trigger" data-node="schedule">
173
+ <div class="flex items-center space-x-3">
174
+ <div class="bg-purple-100 p-2 rounded-md">
175
+ <i class="fas fa-clock text-purple-500"></i>
176
+ </div>
177
+ <div>
178
+ <h4 class="font-medium">Schedule</h4>
179
+ <p class="text-xs text-gray-500">Trigger workflow on schedule</p>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ <h3 class="font-medium text-gray-700 mt-6 mb-2">Actions</h3>
186
+ <div class="space-y-2">
187
+ <div class="node-item bg-white border border-gray-200 rounded-md p-3 cursor-pointer hover:bg-blue-50" data-type="action" data-node="http">
188
+ <div class="flex items-center space-x-3">
189
+ <div class="bg-blue-100 p-2 rounded-md">
190
+ <i class="fas fa-globe text-blue-500"></i>
191
+ </div>
192
+ <div>
193
+ <h4 class="font-medium">HTTP Request</h4>
194
+ <p class="text-xs text-gray-500">Make HTTP request to any URL</p>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ <div class="node-item bg-white border border-gray-200 rounded-md p-3 cursor-pointer hover:bg-blue-50" data-type="action" data-node="email">
199
+ <div class="flex items-center space-x-3">
200
+ <div class="bg-green-100 p-2 rounded-md">
201
+ <i class="fas fa-envelope text-green-500"></i>
202
+ </div>
203
+ <div>
204
+ <h4 class="font-medium">Email</h4>
205
+ <p class="text-xs text-gray-500">Send an email</p>
206
+ </div>
207
+ </div>
208
+ </div>
209
+ <div class="node-item bg-white border border-gray-200 rounded-md p-3 cursor-pointer hover:bg-blue-50" data-type="action" data-node="delay">
210
+ <div class="flex items-center space-x-3">
211
+ <div class="bg-yellow-100 p-2 rounded-md">
212
+ <i class="fas fa-hourglass-half text-yellow-500"></i>
213
+ </div>
214
+ <div>
215
+ <h4 class="font-medium">Delay</h4>
216
+ <p class="text-xs text-gray-500">Delay workflow execution</p>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+
222
+ <h3 class="font-medium text-gray-700 mt-6 mb-2">Logic</h3>
223
+ <div class="space-y-2">
224
+ <div class="node-item bg-white border border-gray-200 rounded-md p-3 cursor-pointer hover:bg-blue-50" data-type="logic" data-node="if">
225
+ <div class="flex items-center space-x-3">
226
+ <div class="bg-indigo-100 p-2 rounded-md">
227
+ <i class="fas fa-code-branch text-indigo-500"></i>
228
+ </div>
229
+ <div>
230
+ <h4 class="font-medium">IF Condition</h4>
231
+ <p class="text-xs text-gray-500">Branch workflow based on condition</p>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ <div class="node-item bg-white border border-gray-200 rounded-md p-3 cursor-pointer hover:bg-blue-50" data-type="logic" data-node="switch">
236
+ <div class="flex items-center space-x-3">
237
+ <div class="bg-pink-100 p-2 rounded-md">
238
+ <i class="fas fa-random text-pink-500"></i>
239
+ </div>
240
+ <div>
241
+ <h4 class="font-medium">Switch</h4>
242
+ <p class="text-xs text-gray-500">Route workflow based on value</p>
243
+ </div>
244
+ </div>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ </div>
249
+
250
+ <!-- Main Workflow Area -->
251
+ <div class="flex-1 relative overflow-hidden">
252
+ <div id="workflow-canvas" class="workflow-canvas w-full h-full relative">
253
+ <svg id="connections-svg" class="absolute top-0 left-0 w-full h-full pointer-events-none" style="z-index: 1;"></svg>
254
+ <div id="nodes-container" class="absolute top-0 left-0 w-full h-full" style="z-index: 2;"></div>
255
+ </div>
256
+
257
+ <!-- Zoom Controls -->
258
+ <div class="absolute bottom-4 right-4 bg-white rounded-md shadow-md p-2 flex flex-col space-y-2">
259
+ <button id="zoom-in" class="p-2 hover:bg-gray-100 rounded">
260
+ <i class="fas fa-search-plus text-gray-600"></i>
261
+ </button>
262
+ <button id="zoom-reset" class="p-2 hover:bg-gray-100 rounded">
263
+ <span class="text-sm font-medium text-gray-600">100%</span>
264
+ </button>
265
+ <button id="zoom-out" class="p-2 hover:bg-gray-100 rounded">
266
+ <i class="fas fa-search-minus text-gray-600"></i>
267
+ </button>
268
+ </div>
269
+ </div>
270
+
271
+ <!-- Right Sidebar - Node Configuration -->
272
+ <div class="w-80 bg-white border-l border-gray-200 flex flex-col sidebar overflow-y-auto">
273
+ <div class="p-4 border-b border-gray-200">
274
+ <h3 class="font-medium text-gray-700">Node Configuration</h3>
275
+ <p class="text-sm text-gray-500" id="selected-node-name">No node selected</p>
276
+ </div>
277
+ <div class="p-4 flex-1" id="node-configuration">
278
+ <div class="text-center py-10 text-gray-400">
279
+ <i class="fas fa-mouse-pointer text-3xl mb-2"></i>
280
+ <p>Select a node to configure</p>
281
+ </div>
282
+ </div>
283
+ <div class="p-4 border-t border-gray-200">
284
+ <button id="delete-node" class="w-full py-2 bg-red-500 text-white rounded-md hover:bg-red-600 transition flex items-center justify-center space-x-2">
285
+ <i class="fas fa-trash"></i>
286
+ <span>Delete Node</span>
287
+ </button>
288
+ </div>
289
+ </div>
290
+ </div>
291
+
292
+ <!-- Tutorial Overlay -->
293
+ <div id="tutorial-overlay" class="tutorial-overlay hidden">
294
+ <div class="tutorial-card">
295
+ <div class="p-6">
296
+ <div class="flex justify-between items-center mb-4">
297
+ <h2 class="text-2xl font-bold text-gray-800">FlowForge Tutorial</h2>
298
+ <button id="close-tutorial" class="text-gray-500 hover:text-gray-700">
299
+ <i class="fas fa-times"></i>
300
+ </button>
301
+ </div>
302
+ <div id="tutorial-content">
303
+ <div class="tutorial-step active" data-step="1">
304
+ <h3 class="text-lg font-medium mb-2">Welcome to FlowForge!</h3>
305
+ <p class="text-gray-600 mb-4">FlowForge is a visual workflow automation tool that helps you connect different services and automate tasks without writing code.</p>
306
+ <p class="text-gray-600 mb-6">This tutorial will guide you through creating your first workflow.</p>
307
+ <div class="flex justify-end">
308
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition next-step">
309
+ Next <i class="fas fa-arrow-right ml-1"></i>
310
+ </button>
311
+ </div>
312
+ </div>
313
+ <div class="tutorial-step hidden" data-step="2">
314
+ <h3 class="text-lg font-medium mb-2">Understanding the Interface</h3>
315
+ <p class="text-gray-600 mb-4">The workspace has three main areas:</p>
316
+ <ul class="list-disc pl-5 text-gray-600 mb-4 space-y-2">
317
+ <li><strong>Left Sidebar:</strong> Contains all available nodes you can add to your workflow</li>
318
+ <li><strong>Canvas:</strong> Where you build your workflow by connecting nodes</li>
319
+ <li><strong>Right Sidebar:</strong> Configure individual nodes when selected</li>
320
+ </ul>
321
+ <div class="flex justify-between">
322
+ <button class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition prev-step">
323
+ <i class="fas fa-arrow-left mr-1"></i> Back
324
+ </button>
325
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition next-step">
326
+ Next <i class="fas fa-arrow-right ml-1"></i>
327
+ </button>
328
+ </div>
329
+ </div>
330
+ <div class="tutorial-step hidden" data-step="3">
331
+ <h3 class="text-lg font-medium mb-2">Creating Your First Workflow</h3>
332
+ <p class="text-gray-600 mb-4">We'll create a simple workflow that:</p>
333
+ <ol class="list-decimal pl-5 text-gray-600 mb-4 space-y-2">
334
+ <li>Triggers on a schedule</li>
335
+ <li>Makes an HTTP request to get data</li>
336
+ <li>Sends an email with the results</li>
337
+ </ol>
338
+ <div class="flex justify-between">
339
+ <button class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition prev-step">
340
+ <i class="fas fa-arrow-left mr-1"></i> Back
341
+ </button>
342
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition next-step">
343
+ Start Building <i class="fas fa-arrow-right ml-1"></i>
344
+ </button>
345
+ </div>
346
+ </div>
347
+ <div class="tutorial-step hidden" data-step="4">
348
+ <h3 class="text-lg font-medium mb-2">Step 1: Add a Trigger</h3>
349
+ <p class="text-gray-600 mb-4">Every workflow needs a trigger to start. Let's add a Schedule trigger that will run our workflow daily.</p>
350
+ <p class="text-gray-600 mb-4 font-medium">👉 Click and drag the "Schedule" node from the left sidebar to the canvas</p>
351
+ <div class="bg-blue-50 border border-blue-200 rounded-md p-4 mb-4">
352
+ <div class="flex items-center space-x-3">
353
+ <div class="bg-purple-100 p-2 rounded-md">
354
+ <i class="fas fa-clock text-purple-500"></i>
355
+ </div>
356
+ <div>
357
+ <h4 class="font-medium">Schedule</h4>
358
+ <p class="text-xs text-gray-500">Trigger workflow on schedule</p>
359
+ </div>
360
+ </div>
361
+ </div>
362
+ <div class="flex justify-between">
363
+ <button class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition prev-step">
364
+ <i class="fas fa-arrow-left mr-1"></i> Back
365
+ </button>
366
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition next-step" disabled id="trigger-added-btn">
367
+ Next <i class="fas fa-arrow-right ml-1"></i>
368
+ </button>
369
+ </div>
370
+ </div>
371
+ <div class="tutorial-step hidden" data-step="5">
372
+ <h3 class="text-lg font-medium mb-2">Step 2: Add an HTTP Request</h3>
373
+ <p class="text-gray-600 mb-4">Now let's add a node that will fetch data from an API when the workflow is triggered.</p>
374
+ <p class="text-gray-600 mb-4 font-medium">👉 Click and drag the "HTTP Request" node to the right of the Schedule node</p>
375
+ <div class="bg-blue-50 border border-blue-200 rounded-md p-4 mb-4">
376
+ <div class="flex items-center space-x-3">
377
+ <div class="bg-blue-100 p-2 rounded-md">
378
+ <i class="fas fa-globe text-blue-500"></i>
379
+ </div>
380
+ <div>
381
+ <h4 class="font-medium">HTTP Request</h4>
382
+ <p class="text-xs text-gray-500">Make HTTP request to any URL</p>
383
+ </div>
384
+ </div>
385
+ </div>
386
+ <div class="flex justify-between">
387
+ <button class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition prev-step">
388
+ <i class="fas fa-arrow-left mr-1"></i> Back
389
+ </button>
390
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition next-step" disabled id="http-added-btn">
391
+ Next <i class="fas fa-arrow-right ml-1"></i>
392
+ </button>
393
+ </div>
394
+ </div>
395
+ <div class="tutorial-step hidden" data-step="6">
396
+ <h3 class="text-lg font-medium mb-2">Step 3: Connect the Nodes</h3>
397
+ <p class="text-gray-600 mb-4">Now we need to connect the Schedule node to the HTTP Request node so the workflow knows the order of operations.</p>
398
+ <p class="text-gray-600 mb-4 font-medium">👉 Click and drag from the output port (right side) of the Schedule node to the input port (left side) of the HTTP Request node</p>
399
+ <div class="flex justify-between">
400
+ <button class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition prev-step">
401
+ <i class="fas fa-arrow-left mr-1"></i> Back
402
+ </button>
403
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition next-step" disabled id="nodes-connected-btn">
404
+ Next <i class="fas fa-arrow-right ml-1"></i>
405
+ </button>
406
+ </div>
407
+ </div>
408
+ <div class="tutorial-step hidden" data-step="7">
409
+ <h3 class="text-lg font-medium mb-2">Step 4: Add an Email Node</h3>
410
+ <p class="text-gray-600 mb-4">Finally, let's add a node that will email us the results from the API request.</p>
411
+ <p class="text-gray-600 mb-4 font-medium">👉 Add an "Email" node to the right of the HTTP Request node and connect them</p>
412
+ <div class="bg-blue-50 border border-blue-200 rounded-md p-4 mb-4">
413
+ <div class="flex items-center space-x-3">
414
+ <div class="bg-green-100 p-2 rounded-md">
415
+ <i class="fas fa-envelope text-green-500"></i>
416
+ </div>
417
+ <div>
418
+ <h4 class="font-medium">Email</h4>
419
+ <p class="text-xs text-gray-500">Send an email</p>
420
+ </div>
421
+ </div>
422
+ </div>
423
+ <div class="flex justify-between">
424
+ <button class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition prev-step">
425
+ <i class="fas fa-arrow-left mr-1"></i> Back
426
+ </button>
427
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition next-step" disabled id="email-added-btn">
428
+ Next <i class="fas fa-arrow-right ml-1"></i>
429
+ </button>
430
+ </div>
431
+ </div>
432
+ <div class="tutorial-step hidden" data-step="8">
433
+ <h3 class="text-lg font-medium mb-2">Step 5: Configure the Nodes</h3>
434
+ <p class="text-gray-600 mb-4">Now let's configure each node:</p>
435
+ <ol class="list-decimal pl-5 text-gray-600 mb-4 space-y-2">
436
+ <li><strong>Schedule:</strong> Double-click to set it to run daily at 9am</li>
437
+ <li><strong>HTTP Request:</strong> Set the URL to an API endpoint</li>
438
+ <li><strong>Email:</strong> Enter your email and a subject/message</li>
439
+ </ol>
440
+ <p class="text-gray-600 mb-4 font-medium">👉 Double-click each node to configure it</p>
441
+ <div class="flex justify-between">
442
+ <button class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition prev-step">
443
+ <i class="fas fa-arrow-left mr-1"></i> Back
444
+ </button>
445
+ <button class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition next-step">
446
+ Next <i class="fas fa-arrow-right ml-1"></i>
447
+ </button>
448
+ </div>
449
+ </div>
450
+ <div class="tutorial-step hidden" data-step="9">
451
+ <h3 class="text-lg font-medium mb-2">Step 6: Execute Your Workflow</h3>
452
+ <p class="text-gray-600 mb-4">Your workflow is now ready to run! You can:</p>
453
+ <ul class="list-disc pl-5 text-gray-600 mb-4 space-y-2">
454
+ <li>Click "Execute" to run it manually</li>
455
+ <li>Wait for the scheduled time (if you configured one)</li>
456
+ <li>Trigger it via webhook (if you used a webhook trigger)</li>
457
+ </ul>
458
+ <p class="text-gray-600 mb-6">Congratulations! You've built your first workflow automation.</p>
459
+ <div class="flex justify-between">
460
+ <button class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition prev-step">
461
+ <i class="fas fa-arrow-left mr-1"></i> Back
462
+ </button>
463
+ <button class="px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 transition finish-tutorial">
464
+ Finish Tutorial <i class="fas fa-check ml-1"></i>
465
+ </button>
466
+ </div>
467
+ </div>
468
+ </div>
469
+ </div>
470
+ </div>
471
+ </div>
472
+
473
+ <script>
474
+ document.addEventListener('DOMContentLoaded', function() {
475
+ // State management
476
+ const state = {
477
+ nodes: [],
478
+ connections: [],
479
+ selectedNode: null,
480
+ draggingNode: null,
481
+ draggingPort: null,
482
+ creatingConnection: false,
483
+ scale: 1,
484
+ offset: { x: 0, y: 0 },
485
+ panning: false,
486
+ startPan: { x: 0, y: 0 },
487
+ tutorial: {
488
+ active: false,
489
+ currentStep: 1,
490
+ highlightElement: null
491
+ }
492
+ };
493
+
494
+ // DOM elements
495
+ const canvas = document.getElementById('workflow-canvas');
496
+ const nodesContainer = document.getElementById('nodes-container');
497
+ const connectionsSvg = document.getElementById('connections-svg');
498
+ const nodeConfiguration = document.getElementById('node-configuration');
499
+ const selectedNodeName = document.getElementById('selected-node-name');
500
+ const deleteNodeBtn = document.getElementById('delete-node');
501
+ const zoomInBtn = document.getElementById('zoom-in');
502
+ const zoomOutBtn = document.getElementById('zoom-out');
503
+ const zoomResetBtn = document.getElementById('zoom-reset');
504
+ const tutorialOverlay = document.getElementById('tutorial-overlay');
505
+ const startTutorialBtn = document.getElementById('start-tutorial');
506
+ const closeTutorialBtn = document.getElementById('close-tutorial');
507
+
508
+ // Node templates
509
+ const nodeTemplates = {
510
+ webhook: {
511
+ name: 'Webhook',
512
+ type: 'trigger',
513
+ icon: 'bolt',
514
+ color: 'red',
515
+ inputs: 0,
516
+ outputs: 1,
517
+ config: `
518
+ <div class="space-y-4">
519
+ <div>
520
+ <label class="block text-sm font-medium text-gray-700 mb-1">Webhook URL</label>
521
+ <div class="flex">
522
+ <input type="text" class="flex-1 border border-gray-300 rounded-l-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" value="https://your-domain.com/webhook">
523
+ <button class="bg-gray-100 border border-l-0 border-gray-300 rounded-r-md px-3 hover:bg-gray-200">
524
+ <i class="fas fa-copy"></i>
525
+ </button>
526
+ </div>
527
+ </div>
528
+ <div>
529
+ <label class="block text-sm font-medium text-gray-700 mb-1">HTTP Method</label>
530
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
531
+ <option>GET</option>
532
+ <option selected>POST</option>
533
+ <option>PUT</option>
534
+ <option>DELETE</option>
535
+ </select>
536
+ </div>
537
+ <div>
538
+ <label class="block text-sm font-medium text-gray-700 mb-1">Response Mode</label>
539
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
540
+ <option>On first node execution</option>
541
+ <option selected>On last node execution</option>
542
+ <option>Manually in node</option>
543
+ </select>
544
+ </div>
545
+ </div>
546
+ `
547
+ },
548
+ schedule: {
549
+ name: 'Schedule',
550
+ type: 'trigger',
551
+ icon: 'clock',
552
+ color: 'purple',
553
+ inputs: 0,
554
+ outputs: 1,
555
+ config: `
556
+ <div class="space-y-4">
557
+ <div>
558
+ <label class="block text-sm font-medium text-gray-700 mb-1">Frequency</label>
559
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
560
+ <option>Minutes</option>
561
+ <option>Hours</option>
562
+ <option selected>Days</option>
563
+ <option>Weeks</option>
564
+ <option>Months</option>
565
+ </select>
566
+ </div>
567
+ <div>
568
+ <label class="block text-sm font-medium text-gray-700 mb-1">Interval</label>
569
+ <input type="number" min="1" value="1" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
570
+ </div>
571
+ <div>
572
+ <label class="block text-sm font-medium text-gray-700 mb-1">Start Time</label>
573
+ <input type="time" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
574
+ </div>
575
+ </div>
576
+ `
577
+ },
578
+ http: {
579
+ name: 'HTTP Request',
580
+ type: 'action',
581
+ icon: 'globe',
582
+ color: 'blue',
583
+ inputs: 1,
584
+ outputs: 1,
585
+ config: `
586
+ <div class="space-y-4">
587
+ <div>
588
+ <label class="block text-sm font-medium text-gray-700 mb-1">URL</label>
589
+ <input type="text" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="https://example.com/api">
590
+ </div>
591
+ <div>
592
+ <label class="block text-sm font-medium text-gray-700 mb-1">Method</label>
593
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
594
+ <option>GET</option>
595
+ <option selected>POST</option>
596
+ <option>PUT</option>
597
+ <option>DELETE</option>
598
+ </select>
599
+ </div>
600
+ <div>
601
+ <label class="block text-sm font-medium text-gray-700 mb-1">Headers</label>
602
+ <div class="border border-gray-300 rounded-md">
603
+ <div class="flex border-b border-gray-300">
604
+ <input type="text" placeholder="Header" class="flex-1 px-2 py-1 border-r border-gray-300 focus:outline-none">
605
+ <input type="text" placeholder="Value" class="flex-1 px-2 py-1 focus:outline-none">
606
+ </div>
607
+ <button class="w-full py-1 text-sm text-blue-500 hover:bg-gray-50">
608
+ <i class="fas fa-plus mr-1"></i> Add Header
609
+ </button>
610
+ </div>
611
+ </div>
612
+ </div>
613
+ `
614
+ },
615
+ email: {
616
+ name: 'Email',
617
+ type: 'action',
618
+ icon: 'envelope',
619
+ color: 'green',
620
+ inputs: 1,
621
+ outputs: 1,
622
+ config: `
623
+ <div class="space-y-4">
624
+ <div>
625
+ <label class="block text-sm font-medium text-gray-700 mb-1">From</label>
626
+ <input type="email" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="[email protected]">
627
+ </div>
628
+ <div>
629
+ <label class="block text-sm font-medium text-gray-700 mb-1">To</label>
630
+ <input type="email" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="[email protected]">
631
+ </div>
632
+ <div>
633
+ <label class="block text-sm font-medium text-gray-700 mb-1">Subject</label>
634
+ <input type="text" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Email subject">
635
+ </div>
636
+ <div>
637
+ <label class="block text-sm font-medium text-gray-700 mb-1">Message</label>
638
+ <textarea class="w-full border border-gray-300 rounded-md px-3 py-2 h-24 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Email content"></textarea>
639
+ </div>
640
+ </div>
641
+ `
642
+ },
643
+ delay: {
644
+ name: 'Delay',
645
+ type: 'action',
646
+ icon: 'hourglass-half',
647
+ color: 'yellow',
648
+ inputs: 1,
649
+ outputs: 1,
650
+ config: `
651
+ <div class="space-y-4">
652
+ <div>
653
+ <label class="block text-sm font-medium text-gray-700 mb-1">Delay Type</label>
654
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
655
+ <option selected>For duration</option>
656
+ <option>Until specific time</option>
657
+ </select>
658
+ </div>
659
+ <div>
660
+ <label class="block text-sm font-medium text-gray-700 mb-1">Duration</label>
661
+ <div class="flex space-x-2">
662
+ <input type="number" min="1" value="5" class="w-20 border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
663
+ <select class="flex-1 border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
664
+ <option>Seconds</option>
665
+ <option selected>Minutes</option>
666
+ <option>Hours</option>
667
+ <option>Days</option>
668
+ </select>
669
+ </div>
670
+ </div>
671
+ </div>
672
+ `
673
+ },
674
+ if: {
675
+ name: 'IF Condition',
676
+ type: 'logic',
677
+ icon: 'code-branch',
678
+ color: 'indigo',
679
+ inputs: 1,
680
+ outputs: 2,
681
+ config: `
682
+ <div class="space-y-4">
683
+ <div>
684
+ <label class="block text-sm font-medium text-gray-700 mb-1">Condition</label>
685
+ <div class="flex items-center space-x-2">
686
+ <select class="flex-1 border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
687
+ <option>Equals</option>
688
+ <option>Not Equals</option>
689
+ <option selected>Contains</option>
690
+ <option>Greater Than</option>
691
+ <option>Less Than</option>
692
+ </select>
693
+ </div>
694
+ </div>
695
+ <div class="grid grid-cols-2 gap-2">
696
+ <div>
697
+ <label class="block text-sm font-medium text-gray-700 mb-1">Value 1</label>
698
+ <input type="text" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Value or expression">
699
+ </div>
700
+ <div>
701
+ <label class="block text-sm font-medium text-gray-700 mb-1">Value 2</label>
702
+ <input type="text" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Value or expression">
703
+ </div>
704
+ </div>
705
+ <div>
706
+ <label class="block text-sm font-medium text-gray-700 mb-1">Output Labels</label>
707
+ <div class="grid grid-cols-2 gap-2">
708
+ <input type="text" value="True" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
709
+ <input type="text" value="False" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
710
+ </div>
711
+ </div>
712
+ </div>
713
+ `
714
+ },
715
+ switch: {
716
+ name: 'Switch',
717
+ type: 'logic',
718
+ icon: 'random',
719
+ color: 'pink',
720
+ inputs: 1,
721
+ outputs: 3,
722
+ config: `
723
+ <div class="space-y-4">
724
+ <div>
725
+ <label class="block text-sm font-medium text-gray-700 mb-1">Routing Mode</label>
726
+ <select class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
727
+ <option selected>Value</option>
728
+ <option>Expression</option>
729
+ <option>Regex</option>
730
+ </select>
731
+ </div>
732
+ <div>
733
+ <label class="block text-sm font-medium text-gray-700 mb-1">Cases</label>
734
+ <div class="border border-gray-300 rounded-md divide-y divide-gray-300">
735
+ <div class="flex items-center p-2">
736
+ <input type="text" value="Case 1" class="flex-1 border border-gray-300 rounded-md px-2 py-1 focus:outline-none">
737
+ <button class="ml-2 text-red-500 hover:text-red-700">
738
+ <i class="fas fa-times"></i>
739
+ </button>
740
+ </div>
741
+ <div class="flex items-center p-2">
742
+ <input type="text" value="Case 2" class="flex-1 border border-gray-300 rounded-md px-2 py-1 focus:outline-none">
743
+ <button class="ml-2 text-red-500 hover:text-red-700">
744
+ <i class="fas fa-times"></i>
745
+ </button>
746
+ </div>
747
+ <button class="w-full py-2 text-sm text-blue-500 hover:bg-gray-50">
748
+ <i class="fas fa-plus mr-1"></i> Add Case
749
+ </button>
750
+ </div>
751
+ </div>
752
+ <div>
753
+ <label class="block text-sm font-medium text-gray-700 mb-1">Default Case</label>
754
+ <input type="text" value="Default" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
755
+ </div>
756
+ </div>
757
+ `
758
+ }
759
+ };
760
+
761
+ // Initialize the app
762
+ function init() {
763
+ // Event listeners for node creation
764
+ document.querySelectorAll('.node-item').forEach(item => {
765
+ item.addEventListener('mousedown', (e) => {
766
+ if (e.button !== 0) return; // Only left click
767
+
768
+ const nodeType = item.dataset.node;
769
+ state.draggingNode = {
770
+ type: nodeType,
771
+ x: e.clientX,
772
+ y: e.clientY
773
+ };
774
+
775
+ document.addEventListener('mousemove', dragNewNode);
776
+ document.addEventListener('mouseup', dropNewNode);
777
+ });
778
+ });
779
+
780
+ // Canvas panning
781
+ canvas.addEventListener('mousedown', (e) => {
782
+ if (e.button === 1 || (e.button === 0 && e.ctrlKey)) { // Middle click or Ctrl+Left click
783
+ e.preventDefault();
784
+ state.panning = true;
785
+ state.startPan = { x: e.clientX, y: e.clientY };
786
+ canvas.style.cursor = 'grabbing';
787
+ }
788
+ });
789
+
790
+ document.addEventListener('mousemove', (e) => {
791
+ if (state.panning) {
792
+ const dx = e.clientX - state.startPan.x;
793
+ const dy = e.clientY - state.startPan.y;
794
+
795
+ state.offset.x += dx;
796
+ state.offset.y += dy;
797
+
798
+ state.startPan = { x: e.clientX, y: e.clientY };
799
+
800
+ updateCanvasTransform();
801
+ }
802
+ });
803
+
804
+ document.addEventListener('mouseup', () => {
805
+ if (state.panning) {
806
+ state.panning = false;
807
+ canvas.style.cursor = '';
808
+ }
809
+ });
810
+
811
+ // Zoom controls
812
+ zoomInBtn.addEventListener('click', () => {
813
+ state.scale = Math.min(state.scale + 0.1, 2);
814
+ updateCanvasTransform();
815
+ });
816
+
817
+ zoomOutBtn.addEventListener('click', () => {
818
+ state.scale = Math.max(state.scale - 0.1, 0.5);
819
+ updateCanvasTransform();
820
+ });
821
+
822
+ zoomResetBtn.addEventListener('click', () => {
823
+ state.scale = 1;
824
+ state.offset = { x: 0, y: 0 };
825
+ updateCanvasTransform();
826
+ });
827
+
828
+ // Delete node
829
+ deleteNodeBtn.addEventListener('click', deleteSelectedNode);
830
+
831
+ // Prevent context menu
832
+ document.addEventListener('contextmenu', (e) => {
833
+ e.preventDefault();
834
+ });
835
+
836
+ // Tutorial controls
837
+ startTutorialBtn.addEventListener('click', startTutorial);
838
+ closeTutorialBtn.addEventListener('click', closeTutorial);
839
+
840
+ // Tutorial navigation
841
+ document.querySelectorAll('.next-step').forEach(btn => {
842
+ btn.addEventListener('click', () => {
843
+ goToStep(state.tutorial.currentStep + 1);
844
+ });
845
+ });
846
+
847
+ document.querySelectorAll('.prev-step').forEach(btn => {
848
+ btn.addEventListener('click', () => {
849
+ goToStep(state.tutorial.currentStep - 1);
850
+ });
851
+ });
852
+
853
+ document.querySelectorAll('.finish-tutorial').forEach(btn => {
854
+ btn.addEventListener('click', closeTutorial);
855
+ });
856
+
857
+ // Start tutorial automatically if no nodes exist
858
+ if (state.nodes.length === 0) {
859
+ setTimeout(() => {
860
+ startTutorial();
861
+ }, 500);
862
+ }
863
+ }
864
+
865
+ // Add a new node to the canvas
866
+ function addNode(nodeType, x, y) {
867
+ const template = nodeTemplates[nodeType];
868
+ if (!template) return;
869
+
870
+ const node = {
871
+ id: state.nodes.length,
872
+ type: nodeType,
873
+ name: template.name,
874
+ x: x,
875
+ y: y,
876
+ width: 200,
877
+ height: 100,
878
+ inputs: template.inputs,
879
+ outputs: template.outputs,
880
+ selected: false
881
+ };
882
+
883
+ state.nodes.push(node);
884
+ renderNode(node);
885
+
886
+ // Check if this was part of the tutorial
887
+ if (state.tutorial.active) {
888
+ if (nodeType === 'schedule' && state.tutorial.currentStep === 4) {
889
+ document.getElementById('trigger-added-btn').disabled = false;
890
+ } else if (nodeType === 'http' && state.tutorial.currentStep === 5) {
891
+ document.getElementById('http-added-btn').disabled = false;
892
+ } else if (nodeType === 'email' && state.tutorial.currentStep === 7) {
893
+ document.getElementById('email-added-btn').disabled = false;
894
+ }
895
+ }
896
+
897
+ return node;
898
+ }
899
+
900
+ // Render a node on the canvas
901
+ function renderNode(node) {
902
+ const template = nodeTemplates[node.type];
903
+
904
+ let nodeEl = document.getElementById(`node-${node.id}`);
905
+ if (!nodeEl) {
906
+ nodeEl = document.createElement('div');
907
+ nodeEl.id = `node-${node.id}`;
908
+ nodeEl.className = `node absolute bg-white rounded-lg shadow-md border-2 ${node.selected ? 'border-blue-500 selected' : 'border-gray-200'}`;
909
+ nodeEl.style.width = `${node.width}px`;
910
+ nodeEl.style.height = `${node.height}px`;
911
+ nodeEl.style.left = `${node.x}px`;
912
+ nodeEl.style.top = `${node.y}px`;
913
+
914
+ // Node header
915
+ const header = document.createElement('div');
916
+ header.className = `flex items-center px-3 py-2 border-b border-gray-200 bg-${template.color}-50 rounded-t-lg`;
917
+
918
+ const icon = document.createElement('div');
919
+ icon.className = `bg-${template.color}-100 p-1 rounded-md mr-2`;
920
+
921
+ const iconEl = document.createElement('i');
922
+ iconEl.className = `fas fa-${template.icon} text-${template.color}-500`;
923
+ icon.appendChild(iconEl);
924
+
925
+ const title = document.createElement('h3');
926
+ title.className = 'font-medium text-sm';
927
+ title.textContent = node.name;
928
+
929
+ header.appendChild(icon);
930
+ header.appendChild(title);
931
+
932
+ // Node body
933
+ const body = document.createElement('div');
934
+ body.className = 'p-3 text-xs text-gray-500';
935
+ body.textContent = 'Double click to configure';
936
+
937
+ // Input ports
938
+ for (let i = 0; i < node.inputs; i++) {
939
+ const port = document.createElement('div');
940
+ port.className = 'node-port input absolute top-1/2';
941
+ port.style.top = `${(i + 1) * (100 / (node.inputs + 1))}%`;
942
+ port.dataset.nodeId = node.id;
943
+ port.dataset.portIndex = i;
944
+ port.dataset.portType = 'input';
945
+
946
+ port.addEventListener('mousedown', startConnection);
947
+ port.addEventListener('mouseup', completeConnection);
948
+
949
+ nodeEl.appendChild(port);
950
+ }
951
+
952
+ // Output ports
953
+ for (let i = 0; i < node.outputs; i++) {
954
+ const port = document.createElement('div');
955
+ port.className = 'node-port output absolute top-1/2';
956
+ port.style.top = `${(i + 1) * (100 / (node.outputs + 1))}%`;
957
+ port.dataset.nodeId = node.id;
958
+ port.dataset.portIndex = i;
959
+ port.dataset.portType = 'output';
960
+
961
+ port.addEventListener('mousedown', startConnection);
962
+ port.addEventListener('mouseup', completeConnection);
963
+
964
+ nodeEl.appendChild(port);
965
+ }
966
+
967
+ // Node events
968
+ nodeEl.addEventListener('mousedown', (e) => {
969
+ if (e.button !== 0) return; // Only left click
970
+
971
+ // Select node
972
+ if (!e.target.classList.contains('node-port')) {
973
+ selectNode(node.id);
974
+
975
+ // Start dragging
976
+ state.draggingNode = node;
977
+ state.dragOffset = {
978
+ x: e.clientX - node.x,
979
+ y: e.clientY - node.y
980
+ };
981
+
982
+ document.addEventListener('mousemove', dragNode);
983
+ document.addEventListener('mouseup', dropNode);
984
+ }
985
+ });
986
+
987
+ nodeEl.addEventListener('dblclick', () => {
988
+ selectNode(node.id);
989
+ showNodeConfiguration(node);
990
+
991
+ // Check if this was part of the tutorial
992
+ if (state.tutorial.active && state.tutorial.currentStep === 8) {
993
+ document.getElementById('nodes-connected-btn').disabled = false;
994
+ }
995
+ });
996
+
997
+ nodeEl.appendChild(header);
998
+ nodeEl.appendChild(body);
999
+
1000
+ nodesContainer.appendChild(nodeEl);
1001
+ } else {
1002
+ // Update existing node
1003
+ nodeEl.style.left = `${node.x}px`;
1004
+ nodeEl.style.top = `${node.y}px`;
1005
+ nodeEl.className = `node absolute bg-white rounded-lg shadow-md border-2 ${node.selected ? 'border-blue-500 selected' : 'border-gray-200'}`;
1006
+ }
1007
+ }
1008
+
1009
+ // Drag a new node from the sidebar
1010
+ function dragNewNode(e) {
1011
+ if (!state.draggingNode) return;
1012
+
1013
+ const ghost = document.getElementById('node-ghost');
1014
+ if (!ghost) {
1015
+ const ghostEl = document.createElement('div');
1016
+ ghostEl.id = 'node-ghost';
1017
+ ghostEl.className = 'node absolute bg-white rounded-lg shadow-md border-2 border-dashed border-gray-400 opacity-70 pointer-events-none';
1018
+ ghostEl.style.width = '200px';
1019
+ ghostEl.style.height = '100px';
1020
+ document.body.appendChild(ghostEl);
1021
+ }
1022
+
1023
+ document.getElementById('node-ghost').style.left = `${e.clientX - 100}px`;
1024
+ document.getElementById('node-ghost').style.top = `${e.clientY - 50}px`;
1025
+ }
1026
+
1027
+ // Drop a new node onto the canvas
1028
+ function dropNewNode(e) {
1029
+ document.removeEventListener('mousemove', dragNewNode);
1030
+ document.removeEventListener('mouseup', dropNewNode);
1031
+
1032
+ const ghost = document.getElementById('node-ghost');
1033
+ if (ghost) ghost.remove();
1034
+
1035
+ if (!state.draggingNode) return;
1036
+
1037
+ // Calculate position relative to canvas
1038
+ const rect = canvas.getBoundingClientRect();
1039
+ const x = e.clientX - rect.left - state.offset.x;
1040
+ const y = e.clientY - rect.top - state.offset.y;
1041
+
1042
+ addNode(state.draggingNode.type, x, y);
1043
+ state.draggingNode = null;
1044
+ }
1045
+
1046
+ // Drag an existing node
1047
+ function dragNode(e) {
1048
+ if (!state.draggingNode) return;
1049
+
1050
+ const node = state.draggingNode;
1051
+ node.x = e.clientX - state.dragOffset.x;
1052
+ node.y = e.clientY - state.dragOffset.y;
1053
+
1054
+ renderNode(node);
1055
+ renderConnections();
1056
+ }
1057
+
1058
+ // Drop an existing node
1059
+ function dropNode() {
1060
+ document.removeEventListener('mousemove', dragNode);
1061
+ document.removeEventListener('mouseup', dropNode);
1062
+ state.draggingNode = null;
1063
+ }
1064
+
1065
+ // Select a node
1066
+ function selectNode(nodeId) {
1067
+ // Deselect all nodes
1068
+ state.nodes.forEach(node => {
1069
+ node.selected = false;
1070
+ });
1071
+
1072
+ // Select the clicked node
1073
+ const node = state.nodes.find(n => n.id === nodeId);
1074
+ if (node) {
1075
+ node.selected = true;
1076
+ state.selectedNode = node;
1077
+ selectedNodeName.textContent = node.name;
1078
+ } else {
1079
+ state.selectedNode = null;
1080
+ selectedNodeName.textContent = 'No node selected';
1081
+ }
1082
+
1083
+ // Re-render all nodes to update selection state
1084
+ state.nodes.forEach(renderNode);
1085
+ }
1086
+
1087
+ // Show node configuration in sidebar
1088
+ function showNodeConfiguration(node) {
1089
+ const template = nodeTemplates[node.type];
1090
+ if (!template) return;
1091
+
1092
+ nodeConfiguration.innerHTML = template.config;
1093
+ }
1094
+
1095
+ // Delete the selected node
1096
+ function deleteSelectedNode() {
1097
+ if (!state.selectedNode) return;
1098
+
1099
+ const nodeId = state.selectedNode.id;
1100
+
1101
+ // Remove connections to/from this node
1102
+ state.connections = state.connections.filter(conn => {
1103
+ return conn.fromNode !== nodeId && conn.toNode !== nodeId;
1104
+ });
1105
+
1106
+ // Remove the node
1107
+ state.nodes = state.nodes.filter(node => node.id !== nodeId);
1108
+
1109
+ // Remove from DOM
1110
+ const nodeEl = document.getElementById(`node-${nodeId}`);
1111
+ if (nodeEl) nodeEl.remove();
1112
+
1113
+ // Clear selection
1114
+ state.selectedNode = null;
1115
+ selectedNodeName.textContent = 'No node selected';
1116
+ nodeConfiguration.innerHTML = `
1117
+ <div class="text-center py-10 text-gray-400">
1118
+ <i class="fas fa-mouse-pointer text-3xl mb-2"></i>
1119
+ <p>Select a node to configure</p>
1120
+ </div>
1121
+ `;
1122
+
1123
+ // Re-render connections
1124
+ renderConnections();
1125
+ }
1126
+
1127
+ // Start creating a connection
1128
+ function startConnection(e) {
1129
+ e.stopPropagation();
1130
+
1131
+ const port = e.target;
1132
+ const nodeId = parseInt(port.dataset.nodeId);
1133
+ const portIndex = parseInt(port.dataset.portIndex);
1134
+ const portType = port.dataset.portType;
1135
+
1136
+ state.draggingPort = {
1137
+ nodeId: nodeId,
1138
+ portIndex: portIndex,
1139
+ portType: portType,
1140
+ x: e.clientX,
1141
+ y: e.clientY
1142
+ };
1143
+
1144
+ state.creatingConnection = true;
1145
+
1146
+ // Create a temporary connection line
1147
+ const tempLine = document.createElement('div');
1148
+ tempLine.id = 'temp-connection';
1149
+ tempLine.style.position = 'absolute';
1150
+ tempLine.style.pointerEvents = 'none';
1151
+ tempLine.style.backgroundColor = '#94a3b8';
1152
+ tempLine.style.height = '2px';
1153
+ tempLine.style.transformOrigin = '0 0';
1154
+ document.body.appendChild(tempLine);
1155
+
1156
+ document.addEventListener('mousemove', drawTempConnection);
1157
+ document.addEventListener('mouseup', cancelConnection);
1158
+ }
1159
+
1160
+ // Draw temporary connection while dragging
1161
+ function drawTempConnection(e) {
1162
+ if (!state.draggingPort || !state.creatingConnection) return;
1163
+
1164
+ const startX = state.draggingPort.x;
1165
+ const startY = state.draggingPort.y;
1166
+ const endX = e.clientX;
1167
+ const endY = e.clientY;
1168
+
1169
+ // Calculate length and angle of the line
1170
+ const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
1171
+ const angle = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI;
1172
+
1173
+ // Update temporary line
1174
+ const tempLine = document.getElementById('temp-connection');
1175
+ tempLine.style.left = `${startX}px`;
1176
+ tempLine.style.top = `${startY}px`;
1177
+ tempLine.style.width = `${length}px`;
1178
+ tempLine.style.transform = `rotate(${angle}deg)`;
1179
+ }
1180
+
1181
+ // Complete a connection between nodes
1182
+ function completeConnection(e) {
1183
+ if (!state.draggingPort || !state.creatingConnection) return;
1184
+
1185
+ const fromPort = state.draggingPort;
1186
+ const toPort = e.target;
1187
+
1188
+ // Only connect output to input
1189
+ if (fromPort.portType === 'input' || toPort.dataset.portType === 'output') {
1190
+ cancelConnection();
1191
+ return;
1192
+ }
1193
+
1194
+ // Don't connect to same node
1195
+ if (fromPort.nodeId === parseInt(toPort.dataset.nodeId)) {
1196
+ cancelConnection();
1197
+ return;
1198
+ }
1199
+
1200
+ // Add the connection
1201
+ addConnection(
1202
+ fromPort.nodeId,
1203
+ fromPort.portIndex,
1204
+ parseInt(toPort.dataset.nodeId),
1205
+ parseInt(toPort.dataset.portIndex)
1206
+ );
1207
+
1208
+ cancelConnection();
1209
+
1210
+ // Check if this was part of the tutorial
1211
+ if (state.tutorial.active && state.tutorial.currentStep === 6) {
1212
+ document.getElementById('nodes-connected-btn').disabled = false;
1213
+ }
1214
+ }
1215
+
1216
+ // Cancel connection creation
1217
+ function cancelConnection() {
1218
+ document.removeEventListener('mousemove', drawTempConnection);
1219
+ document.removeEventListener('mouseup', cancelConnection);
1220
+
1221
+ const tempLine = document.getElementById('temp-connection');
1222
+ if (tempLine) tempLine.remove();
1223
+
1224
+ state.draggingPort = null;
1225
+ state.creatingConnection = false;
1226
+ }
1227
+
1228
+ // Add a connection between nodes
1229
+ function addConnection(fromNodeId, fromPortIndex, toNodeId, toPortIndex) {
1230
+ // Check if connection already exists
1231
+ const exists = state.connections.some(conn => {
1232
+ return conn.fromNode === fromNodeId &&
1233
+ conn.fromPort === fromPortIndex &&
1234
+ conn.toNode === toNodeId &&
1235
+ conn.toPort === toPortIndex;
1236
+ });
1237
+
1238
+ if (exists) return;
1239
+
1240
+ state.connections.push({
1241
+ fromNode: fromNodeId,
1242
+ fromPort: fromPortIndex,
1243
+ toNode: toNodeId,
1244
+ toPort: toPortIndex
1245
+ });
1246
+
1247
+ renderConnections();
1248
+ }
1249
+
1250
+ // Render all connections on the canvas
1251
+ function renderConnections() {
1252
+ // Clear existing connections
1253
+ connectionsSvg.innerHTML = '';
1254
+
1255
+ state.connections.forEach(conn => {
1256
+ const fromNode = state.nodes.find(n => n.id === conn.fromNode);
1257
+ const toNode = state.nodes.find(n => n.id === conn.toNode);
1258
+
1259
+ if (!fromNode || !toNode) return;
1260
+
1261
+ // Calculate port positions
1262
+ const fromPortX = fromNode.x + fromNode.width;
1263
+ const fromPortY = fromNode.y + (conn.fromPort + 1) * (fromNode.height / (fromNode.outputs + 1));
1264
+
1265
+ const toPortX = toNode.x;
1266
+ const toPortY = toNode.y + (conn.toPort + 1) * (toNode.height / (toNode.inputs + 1));
1267
+
1268
+ // Create a smooth bezier curve between nodes
1269
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1270
+ const midX = (fromPortX + toPortX) / 2;
1271
+
1272
+ path.setAttribute('d', `M${fromPortX},${fromPortY} C${midX},${fromPortY} ${midX},${toPortY} ${toPortX},${toPortY}`);
1273
+ path.setAttribute('class', 'connection-path');
1274
+
1275
+ connectionsSvg.appendChild(path);
1276
+ });
1277
+ }
1278
+
1279
+ // Update canvas transform for pan/zoom
1280
+ function updateCanvasTransform() {
1281
+ nodesContainer.style.transform = `translate(${state.offset.x}px, ${state.offset.y}px) scale(${state.scale})`;
1282
+ connectionsSvg.style.transform = `translate(${state.offset.x}px, ${state.offset.y}px) scale(${state.scale})`;
1283
+ zoomResetBtn.querySelector('span').textContent = `${Math.round(state.scale * 100)}%`;
1284
+ }
1285
+
1286
+ // Tutorial functions
1287
+ function startTutorial() {
1288
+ state.tutorial.active = true;
1289
+ state.tutorial.currentStep = 1;
1290
+ tutorialOverlay.classList.remove('hidden');
1291
+ goToStep(1);
1292
+ }
1293
+
1294
+ function closeTutorial() {
1295
+ state.tutorial.active = false;
1296
+ tutorialOverlay.classList.add('hidden');
1297
+ removeHighlight();
1298
+ }
1299
+
1300
+ function goToStep(step) {
1301
+ if (step < 1 || step > 9) return;
1302
+
1303
+ state.tutorial.currentStep = step;
1304
+
1305
+ // Update UI
1306
+ document.querySelectorAll('.tutorial-step').forEach(el => {
1307
+ el.classList.add('hidden');
1308
+ el.classList.remove('active');
1309
+ });
1310
+
1311
+ const currentStepEl = document.querySelector(`.tutorial-step[data-step="${step}"]`);
1312
+ if (currentStepEl) {
1313
+ currentStepEl.classList.remove('hidden');
1314
+ currentStepEl.classList.add('active');
1315
+ }
1316
+
1317
+ // Handle step-specific UI
1318
+ if (step === 4) {
1319
+ highlightElement('.node-item[data-node="schedule"]', 'Drag this node to the canvas');
1320
+ } else if (step === 5) {
1321
+ highlightElement('.node-item[data-node="http"]', 'Drag this node to the canvas');
1322
+ } else if (step === 6) {
1323
+ highlightElement('.node-port.output', 'Drag from this port to connect nodes');
1324
+ } else if (step === 7) {
1325
+ highlightElement('.node-item[data-node="email"]', 'Drag this node to the canvas');
1326
+ } else if (step === 8) {
1327
+ highlightElement('.node', 'Double-click a node to configure it');
1328
+ } else {
1329
+ removeHighlight();
1330
+ }
1331
+ }
1332
+
1333
+ function highlightElement(selector, text) {
1334
+ removeHighlight();
1335
+
1336
+ const element = document.querySelector(selector);
1337
+ if (!element) return;
1338
+
1339
+ const rect = element.getBoundingClientRect();
1340
+
1341
+ // Create highlight
1342
+ const highlight = document.createElement('div');
1343
+ highlight.className = 'tutorial-highlight animate-pulse';
1344
+ highlight.style.width = `${rect.width + 16}px`;
1345
+ highlight.style.height = `${rect.height + 16}px`;
1346
+ highlight.style.top = `${rect.top - 8 + window.scrollY}px`;
1347
+ highlight.style.left = `${rect.left - 8 + window.scrollX}px`;
1348
+ document.body.appendChild(highlight);
1349
+
1350
+ // Create tooltip
1351
+ const tooltip = document.createElement('div');
1352
+ tooltip.className = 'tutorial-tooltip';
1353
+ tooltip.textContent = text;
1354
+
1355
+ // Position tooltip below the element
1356
+ tooltip.style.top = `${rect.bottom + 8 + window.scrollY}px`;
1357
+ tooltip.style.left = `${rect.left + window.scrollX}px`;
1358
+ document.body.appendChild(tooltip);
1359
+
1360
+ state.tutorial.highlightElement = { highlight, tooltip };
1361
+ }
1362
+
1363
+ function removeHighlight() {
1364
+ if (state.tutorial.highlightElement) {
1365
+ state.tutorial.highlightElement.highlight.remove();
1366
+ state.tutorial.highlightElement.tooltip.remove();
1367
+ state.tutorial.highlightElement = null;
1368
+ }
1369
+ }
1370
+
1371
+ // Initialize the application
1372
+ init();
1373
+ });
1374
+ </script>
1375
+ <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=theaimoron/my-attempt-at-automation" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1376
+ </html>
prompts.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ build me my own fully functional version of a n8n
2
+ this is good but i would like to have a tutorial before i start using as this is just for me and i never used an n8n before