jayavibhav commited on
Commit
3f10e1f
·
verified ·
1 Parent(s): 61ef924

Upload folder using huggingface_hub

Browse files
.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eggs/
2
+ dist/
3
+ *.pyc
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ __tmp/*
8
+ *.pyi
9
+ .mypycache
10
+ .ruff_cache
11
+ node_modules
12
+ backend/**/templates/
README.md CHANGED
@@ -1,12 +1,366 @@
1
  ---
2
- title: Gradio Workflowbuilder
3
- emoji: 😻
4
- colorFrom: pink
5
- colorTo: purple
 
6
  sdk: gradio
7
- sdk_version: 5.33.1
8
- app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ tags: [gradio-custom-component, SimpleTextbox, workflow, builder, editor]
3
+ title: gradio_workflowbuilder
4
+ short_description: workflow builder
5
+ colorFrom: blue
6
+ colorTo: yellow
7
  sdk: gradio
 
 
8
  pinned: false
9
+ app_file: space.py
10
  ---
11
 
12
+ # `gradio_workflowbuilder`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
14
+
15
+ workflow builder
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install gradio_workflowbuilder
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ import gradio as gr
27
+ from gradio_workflowbuilder import WorkflowBuilder
28
+ import json
29
+
30
+
31
+ def export_workflow(workflow_data):
32
+ """Export workflow as formatted JSON"""
33
+ if not workflow_data:
34
+ return "No workflow to export"
35
+ return json.dumps(workflow_data, indent=2)
36
+
37
+
38
+ # Create the main interface
39
+ with gr.Blocks(
40
+ title="🎨 Visual Workflow Builder",
41
+ theme=gr.themes.Soft(),
42
+ css="""
43
+ .main-container { max-width: 1600px; margin: 0 auto; }
44
+ .workflow-section { margin-bottom: 2rem; }
45
+ .button-row { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
46
+
47
+ .component-description {
48
+ padding: 24px;
49
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
50
+ border-radius: 12px;
51
+ border-left: 4px solid #3b82f6;
52
+ margin: 16px 0;
53
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
54
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
55
+ }
56
+
57
+ .component-description p {
58
+ margin: 10px 0;
59
+ line-height: 1.6;
60
+ color: #374151;
61
+ }
62
+
63
+ .base-description {
64
+ font-size: 17px;
65
+ font-weight: 600;
66
+ color: #1e293b;
67
+ }
68
+
69
+ .base-description strong {
70
+ color: #3b82f6;
71
+ font-weight: 700;
72
+ }
73
+
74
+ .feature-description {
75
+ font-size: 16px;
76
+ color: #1e293b;
77
+ font-weight: 500;
78
+ }
79
+
80
+ .customization-note {
81
+ font-size: 15px;
82
+ color: #64748b;
83
+ font-style: italic;
84
+ }
85
+
86
+ .sample-intro {
87
+ font-size: 15px;
88
+ color: #1e293b;
89
+ font-weight: 600;
90
+ margin-top: 16px;
91
+ border-top: 1px solid #e2e8f0;
92
+ padding-top: 16px;
93
+ }
94
+ """
95
+ ) as demo:
96
+
97
+ with gr.Column(elem_classes=["main-container"]):
98
+ gr.Markdown("""
99
+ # 🎨 Visual Workflow Builder
100
+
101
+ **Create sophisticated workflows with drag and drop simplicity.**
102
+ """)
103
+
104
+ # Simple description section with styling
105
+ gr.HTML("""
106
+ <div class="component-description">
107
+ <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
108
+ <p class="feature-description">Create custom workflows.</p>
109
+ <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
110
+ <p class="sample-intro">Here is a sample:</p>
111
+ </div>
112
+ """)
113
+
114
+ # Main workflow builder section
115
+ with gr.Column(elem_classes=["workflow-section"]):
116
+ workflow_builder = WorkflowBuilder(
117
+ label="🎨 Visual Workflow Designer",
118
+ info="Drag components from the sidebar → Connect nodes by dragging from output (right) to input (left) → Click nodes to edit properties"
119
+ )
120
+
121
+ # Export section below the workflow
122
+ gr.Markdown("## 💾 Export Workflow")
123
+
124
+ with gr.Row():
125
+ with gr.Column():
126
+ export_output = gr.Code(
127
+ language="json",
128
+ label="Workflow Configuration",
129
+ lines=10
130
+ )
131
+
132
+ # Action button
133
+ with gr.Row(elem_classes=["button-row"]):
134
+ export_btn = gr.Button("💾 Export JSON", variant="primary", size="lg")
135
+
136
+ # Instructions
137
+ with gr.Accordion("📖 How to Use", open=False):
138
+ gr.Markdown("""
139
+ ### 🚀 Getting Started
140
+
141
+ 1. **Add Components**: Drag items from the left sidebar onto the canvas
142
+ 2. **Connect Nodes**: Drag from the blue circle on the right of a node to the left circle of another
143
+ 3. **Edit Properties**: Click any node to see its editable properties on the right panel
144
+ 4. **Organize**: Drag nodes around to create a clean workflow layout
145
+ 5. **Delete**: Use the ✕ button on nodes or click the red circle on connections
146
+
147
+ ### 🎯 Component Types
148
+
149
+ - **📥 Inputs**: Text fields, file uploads, number inputs
150
+ - **⚙️ Processing**: Language models, text processors, conditionals
151
+ - **📤 Outputs**: Text displays, file exports, charts
152
+ - **🔧 Tools**: API calls, data transformers, validators
153
+
154
+ ### 💡 Pro Tips
155
+
156
+ - **Collapsible Panels**: Use the arrow buttons to hide/show sidebars
157
+ - **Live Updates**: Properties update in real-time as you edit
158
+ - **Export Options**: Get JSON config for your workflow
159
+ """)
160
+
161
+ # Event handlers
162
+ export_btn.click(
163
+ fn=export_workflow,
164
+ inputs=[workflow_builder],
165
+ outputs=[export_output]
166
+ )
167
+
168
+
169
+ if __name__ == "__main__":
170
+ demo.launch(
171
+ server_name="0.0.0.0",
172
+ show_error=True
173
+ )
174
+
175
+ ```
176
+
177
+ ## `WorkflowBuilder`
178
+
179
+ ### Initialization
180
+
181
+ <table>
182
+ <thead>
183
+ <tr>
184
+ <th align="left">name</th>
185
+ <th align="left" style="width: 25%;">type</th>
186
+ <th align="left">default</th>
187
+ <th align="left">description</th>
188
+ </tr>
189
+ </thead>
190
+ <tbody>
191
+ <tr>
192
+ <td align="left"><code>value</code></td>
193
+ <td align="left" style="width: 25%;">
194
+
195
+ ```python
196
+ typing.Optional[typing.Dict[str, typing.Any]][
197
+ typing.Dict[str, typing.Any][str, typing.Any], None
198
+ ]
199
+ ```
200
+
201
+ </td>
202
+ <td align="left"><code>None</code></td>
203
+ <td align="left">Default workflow data with nodes and edges</td>
204
+ </tr>
205
+
206
+ <tr>
207
+ <td align="left"><code>label</code></td>
208
+ <td align="left" style="width: 25%;">
209
+
210
+ ```python
211
+ typing.Optional[str][str, None]
212
+ ```
213
+
214
+ </td>
215
+ <td align="left"><code>None</code></td>
216
+ <td align="left">Component label</td>
217
+ </tr>
218
+
219
+ <tr>
220
+ <td align="left"><code>info</code></td>
221
+ <td align="left" style="width: 25%;">
222
+
223
+ ```python
224
+ typing.Optional[str][str, None]
225
+ ```
226
+
227
+ </td>
228
+ <td align="left"><code>None</code></td>
229
+ <td align="left">Additional component information</td>
230
+ </tr>
231
+
232
+ <tr>
233
+ <td align="left"><code>show_label</code></td>
234
+ <td align="left" style="width: 25%;">
235
+
236
+ ```python
237
+ typing.Optional[bool][bool, None]
238
+ ```
239
+
240
+ </td>
241
+ <td align="left"><code>None</code></td>
242
+ <td align="left">Whether to show the label</td>
243
+ </tr>
244
+
245
+ <tr>
246
+ <td align="left"><code>container</code></td>
247
+ <td align="left" style="width: 25%;">
248
+
249
+ ```python
250
+ bool
251
+ ```
252
+
253
+ </td>
254
+ <td align="left"><code>True</code></td>
255
+ <td align="left">Whether to use container styling</td>
256
+ </tr>
257
+
258
+ <tr>
259
+ <td align="left"><code>scale</code></td>
260
+ <td align="left" style="width: 25%;">
261
+
262
+ ```python
263
+ typing.Optional[int][int, None]
264
+ ```
265
+
266
+ </td>
267
+ <td align="left"><code>None</code></td>
268
+ <td align="left">Relative width scale</td>
269
+ </tr>
270
+
271
+ <tr>
272
+ <td align="left"><code>min_width</code></td>
273
+ <td align="left" style="width: 25%;">
274
+
275
+ ```python
276
+ int
277
+ ```
278
+
279
+ </td>
280
+ <td align="left"><code>160</code></td>
281
+ <td align="left">Minimum width in pixels</td>
282
+ </tr>
283
+
284
+ <tr>
285
+ <td align="left"><code>visible</code></td>
286
+ <td align="left" style="width: 25%;">
287
+
288
+ ```python
289
+ bool
290
+ ```
291
+
292
+ </td>
293
+ <td align="left"><code>True</code></td>
294
+ <td align="left">Whether component is visible</td>
295
+ </tr>
296
+
297
+ <tr>
298
+ <td align="left"><code>elem_id</code></td>
299
+ <td align="left" style="width: 25%;">
300
+
301
+ ```python
302
+ typing.Optional[str][str, None]
303
+ ```
304
+
305
+ </td>
306
+ <td align="left"><code>None</code></td>
307
+ <td align="left">HTML element ID</td>
308
+ </tr>
309
+
310
+ <tr>
311
+ <td align="left"><code>elem_classes</code></td>
312
+ <td align="left" style="width: 25%;">
313
+
314
+ ```python
315
+ typing.Optional[typing.List[str]][
316
+ typing.List[str][str], None
317
+ ]
318
+ ```
319
+
320
+ </td>
321
+ <td align="left"><code>None</code></td>
322
+ <td align="left">CSS classes</td>
323
+ </tr>
324
+
325
+ <tr>
326
+ <td align="left"><code>render</code></td>
327
+ <td align="left" style="width: 25%;">
328
+
329
+ ```python
330
+ bool
331
+ ```
332
+
333
+ </td>
334
+ <td align="left"><code>True</code></td>
335
+ <td align="left">Whether to render immediately</td>
336
+ </tr>
337
+ </tbody></table>
338
+
339
+
340
+ ### Events
341
+
342
+ | name | description |
343
+ |:-----|:------------|
344
+ | `change` | Triggered when the value of the WorkflowBuilder changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
345
+ | `input` | This listener is triggered when the user changes the value of the WorkflowBuilder. |
346
+
347
+
348
+
349
+ ### User function
350
+
351
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
352
+
353
+ - When used as an Input, the component only impacts the input signature of the user function.
354
+ - When used as an output, the component only impacts the return signature of the user function.
355
+
356
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
357
+
358
+
359
+
360
+ ```python
361
+ def predict(
362
+ value: typing.Dict[str, typing.Any][str, typing.Any]
363
+ ) -> typing.Dict[str, typing.Any][str, typing.Any]:
364
+ return value
365
+ ```
366
+
__init__.py ADDED
File without changes
app.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from gradio_workflowbuilder import WorkflowBuilder
3
+ import json
4
+
5
+
6
+ def export_workflow(workflow_data):
7
+ """Export workflow as formatted JSON"""
8
+ if not workflow_data:
9
+ return "No workflow to export"
10
+ return json.dumps(workflow_data, indent=2)
11
+
12
+
13
+ # Create the main interface
14
+ with gr.Blocks(
15
+ title="🎨 Visual Workflow Builder",
16
+ theme=gr.themes.Soft(),
17
+ css="""
18
+ .main-container { max-width: 1600px; margin: 0 auto; }
19
+ .workflow-section { margin-bottom: 2rem; }
20
+ .button-row { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
21
+
22
+ .component-description {
23
+ padding: 24px;
24
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
25
+ border-radius: 12px;
26
+ border-left: 4px solid #3b82f6;
27
+ margin: 16px 0;
28
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
29
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
30
+ }
31
+
32
+ .component-description p {
33
+ margin: 10px 0;
34
+ line-height: 1.6;
35
+ color: #374151;
36
+ }
37
+
38
+ .base-description {
39
+ font-size: 17px;
40
+ font-weight: 600;
41
+ color: #1e293b;
42
+ }
43
+
44
+ .base-description strong {
45
+ color: #3b82f6;
46
+ font-weight: 700;
47
+ }
48
+
49
+ .feature-description {
50
+ font-size: 16px;
51
+ color: #1e293b;
52
+ font-weight: 500;
53
+ }
54
+
55
+ .customization-note {
56
+ font-size: 15px;
57
+ color: #64748b;
58
+ font-style: italic;
59
+ }
60
+
61
+ .sample-intro {
62
+ font-size: 15px;
63
+ color: #1e293b;
64
+ font-weight: 600;
65
+ margin-top: 16px;
66
+ border-top: 1px solid #e2e8f0;
67
+ padding-top: 16px;
68
+ }
69
+ """
70
+ ) as demo:
71
+
72
+ with gr.Column(elem_classes=["main-container"]):
73
+ gr.Markdown("""
74
+ # 🎨 Visual Workflow Builder
75
+
76
+ **Create sophisticated workflows with drag and drop simplicity.**
77
+ """)
78
+
79
+ # Simple description section with styling
80
+ gr.HTML("""
81
+ <div class="component-description">
82
+ <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
83
+ <p class="feature-description">Create custom workflows.</p>
84
+ <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
85
+ <p class="sample-intro">Here is a sample:</p>
86
+ </div>
87
+ """)
88
+
89
+ # Main workflow builder section
90
+ with gr.Column(elem_classes=["workflow-section"]):
91
+ workflow_builder = WorkflowBuilder(
92
+ label="🎨 Visual Workflow Designer",
93
+ info="Drag components from the sidebar → Connect nodes by dragging from output (right) to input (left) → Click nodes to edit properties"
94
+ )
95
+
96
+ # Export section below the workflow
97
+ gr.Markdown("## 💾 Export Workflow")
98
+
99
+ with gr.Row():
100
+ with gr.Column():
101
+ export_output = gr.Code(
102
+ language="json",
103
+ label="Workflow Configuration",
104
+ lines=10
105
+ )
106
+
107
+ # Action button
108
+ with gr.Row(elem_classes=["button-row"]):
109
+ export_btn = gr.Button("💾 Export JSON", variant="primary", size="lg")
110
+
111
+ # Instructions
112
+ with gr.Accordion("📖 How to Use", open=False):
113
+ gr.Markdown("""
114
+ ### 🚀 Getting Started
115
+
116
+ 1. **Add Components**: Drag items from the left sidebar onto the canvas
117
+ 2. **Connect Nodes**: Drag from the blue circle on the right of a node to the left circle of another
118
+ 3. **Edit Properties**: Click any node to see its editable properties on the right panel
119
+ 4. **Organize**: Drag nodes around to create a clean workflow layout
120
+ 5. **Delete**: Use the ✕ button on nodes or click the red circle on connections
121
+
122
+ ### 🎯 Component Types
123
+
124
+ - **📥 Inputs**: Text fields, file uploads, number inputs
125
+ - **⚙️ Processing**: Language models, text processors, conditionals
126
+ - **📤 Outputs**: Text displays, file exports, charts
127
+ - **🔧 Tools**: API calls, data transformers, validators
128
+
129
+ ### 💡 Pro Tips
130
+
131
+ - **Collapsible Panels**: Use the arrow buttons to hide/show sidebars
132
+ - **Live Updates**: Properties update in real-time as you edit
133
+ - **Export Options**: Get JSON config for your workflow
134
+ """)
135
+
136
+ # Event handlers
137
+ export_btn.click(
138
+ fn=export_workflow,
139
+ inputs=[workflow_builder],
140
+ outputs=[export_output]
141
+ )
142
+
143
+
144
+ if __name__ == "__main__":
145
+ demo.launch(
146
+ server_name="0.0.0.0",
147
+ show_error=True
148
+ )
css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_workflowbuilder
space.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'WorkflowBuilder': {'description': 'Professional Workflow Builder component with support for 25+ node types\ninspired by n8n and Langflow for AI agent development and MCP integration.', 'members': {'__init__': {'value': {'type': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, typing.Any], None\n]', 'default': 'None', 'description': 'Default workflow data with nodes and edges'}, 'label': {'type': 'typing.Optional[str][str, None]', 'default': 'None', 'description': 'Component label'}, 'info': {'type': 'typing.Optional[str][str, None]', 'default': 'None', 'description': 'Additional component information'}, 'show_label': {'type': 'typing.Optional[bool][bool, None]', 'default': 'None', 'description': 'Whether to show the label'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'Whether to use container styling'}, 'scale': {'type': 'typing.Optional[int][int, None]', 'default': 'None', 'description': 'Relative width scale'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'Minimum width in pixels'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'Whether component is visible'}, 'elem_id': {'type': 'typing.Optional[str][str, None]', 'default': 'None', 'description': 'HTML element ID'}, 'elem_classes': {'type': 'typing.Optional[typing.List[str]][\n typing.List[str][str], None\n]', 'default': 'None', 'description': 'CSS classes'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'Whether to render immediately'}}, 'postprocess': {'value': {'type': 'typing.Dict[str, typing.Any][str, typing.Any]', 'description': None}}, 'preprocess': {'return': {'type': 'typing.Dict[str, typing.Any][str, typing.Any]', 'description': None}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the WorkflowBuilder changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'input': {'type': None, 'default': None, 'description': 'This listener is triggered when the user changes the value of the WorkflowBuilder.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'WorkflowBuilder': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Default(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_workflowbuilder`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
25
+ </div>
26
+
27
+ workflow builder
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_workflowbuilder
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+ import gradio as gr
42
+ from gradio_workflowbuilder import WorkflowBuilder
43
+ import json
44
+
45
+
46
+ def export_workflow(workflow_data):
47
+ \"\"\"Export workflow as formatted JSON\"\"\"
48
+ if not workflow_data:
49
+ return "No workflow to export"
50
+ return json.dumps(workflow_data, indent=2)
51
+
52
+
53
+ # Create the main interface
54
+ with gr.Blocks(
55
+ title="🎨 Visual Workflow Builder",
56
+ theme=gr.themes.Soft(),
57
+ css=\"\"\"
58
+ .main-container { max-width: 1600px; margin: 0 auto; }
59
+ .workflow-section { margin-bottom: 2rem; }
60
+ .button-row { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
61
+
62
+ .component-description {
63
+ padding: 24px;
64
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
65
+ border-radius: 12px;
66
+ border-left: 4px solid #3b82f6;
67
+ margin: 16px 0;
68
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
69
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
70
+ }
71
+
72
+ .component-description p {
73
+ margin: 10px 0;
74
+ line-height: 1.6;
75
+ color: #374151;
76
+ }
77
+
78
+ .base-description {
79
+ font-size: 17px;
80
+ font-weight: 600;
81
+ color: #1e293b;
82
+ }
83
+
84
+ .base-description strong {
85
+ color: #3b82f6;
86
+ font-weight: 700;
87
+ }
88
+
89
+ .feature-description {
90
+ font-size: 16px;
91
+ color: #1e293b;
92
+ font-weight: 500;
93
+ }
94
+
95
+ .customization-note {
96
+ font-size: 15px;
97
+ color: #64748b;
98
+ font-style: italic;
99
+ }
100
+
101
+ .sample-intro {
102
+ font-size: 15px;
103
+ color: #1e293b;
104
+ font-weight: 600;
105
+ margin-top: 16px;
106
+ border-top: 1px solid #e2e8f0;
107
+ padding-top: 16px;
108
+ }
109
+ \"\"\"
110
+ ) as demo:
111
+
112
+ with gr.Column(elem_classes=["main-container"]):
113
+ gr.Markdown(\"\"\"
114
+ # 🎨 Visual Workflow Builder
115
+
116
+ **Create sophisticated workflows with drag and drop simplicity.**
117
+ \"\"\")
118
+
119
+ # Simple description section with styling
120
+ gr.HTML(\"\"\"
121
+ <div class="component-description">
122
+ <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
123
+ <p class="feature-description">Create custom workflows.</p>
124
+ <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
125
+ <p class="sample-intro">Here is a sample:</p>
126
+ </div>
127
+ \"\"\")
128
+
129
+ # Main workflow builder section
130
+ with gr.Column(elem_classes=["workflow-section"]):
131
+ workflow_builder = WorkflowBuilder(
132
+ label="🎨 Visual Workflow Designer",
133
+ info="Drag components from the sidebar → Connect nodes by dragging from output (right) to input (left) → Click nodes to edit properties"
134
+ )
135
+
136
+ # Export section below the workflow
137
+ gr.Markdown("## 💾 Export Workflow")
138
+
139
+ with gr.Row():
140
+ with gr.Column():
141
+ export_output = gr.Code(
142
+ language="json",
143
+ label="Workflow Configuration",
144
+ lines=10
145
+ )
146
+
147
+ # Action button
148
+ with gr.Row(elem_classes=["button-row"]):
149
+ export_btn = gr.Button("💾 Export JSON", variant="primary", size="lg")
150
+
151
+ # Instructions
152
+ with gr.Accordion("📖 How to Use", open=False):
153
+ gr.Markdown(\"\"\"
154
+ ### 🚀 Getting Started
155
+
156
+ 1. **Add Components**: Drag items from the left sidebar onto the canvas
157
+ 2. **Connect Nodes**: Drag from the blue circle on the right of a node to the left circle of another
158
+ 3. **Edit Properties**: Click any node to see its editable properties on the right panel
159
+ 4. **Organize**: Drag nodes around to create a clean workflow layout
160
+ 5. **Delete**: Use the ✕ button on nodes or click the red circle on connections
161
+
162
+ ### 🎯 Component Types
163
+
164
+ - **📥 Inputs**: Text fields, file uploads, number inputs
165
+ - **⚙️ Processing**: Language models, text processors, conditionals
166
+ - **📤 Outputs**: Text displays, file exports, charts
167
+ - **🔧 Tools**: API calls, data transformers, validators
168
+
169
+ ### 💡 Pro Tips
170
+
171
+ - **Collapsible Panels**: Use the arrow buttons to hide/show sidebars
172
+ - **Live Updates**: Properties update in real-time as you edit
173
+ - **Export Options**: Get JSON config for your workflow
174
+ \"\"\")
175
+
176
+ # Event handlers
177
+ export_btn.click(
178
+ fn=export_workflow,
179
+ inputs=[workflow_builder],
180
+ outputs=[export_output]
181
+ )
182
+
183
+
184
+ if __name__ == "__main__":
185
+ demo.launch(
186
+ server_name="0.0.0.0",
187
+ show_error=True
188
+ )
189
+
190
+ ```
191
+ """, elem_classes=["md-custom"], header_links=True)
192
+
193
+
194
+ gr.Markdown("""
195
+ ## `WorkflowBuilder`
196
+
197
+ ### Initialization
198
+ """, elem_classes=["md-custom"], header_links=True)
199
+
200
+ gr.ParamViewer(value=_docs["WorkflowBuilder"]["members"]["__init__"], linkify=[])
201
+
202
+
203
+ gr.Markdown("### Events")
204
+ gr.ParamViewer(value=_docs["WorkflowBuilder"]["events"], linkify=['Event'])
205
+
206
+
207
+
208
+
209
+ gr.Markdown("""
210
+
211
+ ### User function
212
+
213
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
214
+
215
+ - When used as an Input, the component only impacts the input signature of the user function.
216
+ - When used as an output, the component only impacts the return signature of the user function.
217
+
218
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
219
+
220
+
221
+
222
+ ```python
223
+ def predict(
224
+ value: typing.Dict[str, typing.Any][str, typing.Any]
225
+ ) -> typing.Dict[str, typing.Any][str, typing.Any]:
226
+ return value
227
+ ```
228
+ """, elem_classes=["md-custom", "WorkflowBuilder-user-fn"], header_links=True)
229
+
230
+
231
+
232
+
233
+ demo.load(None, js=r"""function() {
234
+ const refs = {};
235
+ const user_fn_refs = {
236
+ WorkflowBuilder: [], };
237
+ requestAnimationFrame(() => {
238
+
239
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
240
+ if (refs.length > 0) {
241
+ const el = document.querySelector(`.${key}-user-fn`);
242
+ if (!el) return;
243
+ refs.forEach(ref => {
244
+ el.innerHTML = el.innerHTML.replace(
245
+ new RegExp("\\b"+ref+"\\b", "g"),
246
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
247
+ );
248
+ })
249
+ }
250
+ })
251
+
252
+ Object.entries(refs).forEach(([key, refs]) => {
253
+ if (refs.length > 0) {
254
+ const el = document.querySelector(`.${key}`);
255
+ if (!el) return;
256
+ refs.forEach(ref => {
257
+ el.innerHTML = el.innerHTML.replace(
258
+ new RegExp("\\b"+ref+"\\b", "g"),
259
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
260
+ );
261
+ })
262
+ }
263
+ })
264
+ })
265
+ }
266
+
267
+ """)
268
+
269
+ demo.launch()
src/.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .eggs/
2
+ dist/
3
+ *.pyc
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+ __tmp/*
8
+ *.pyi
9
+ .mypycache
10
+ .ruff_cache
11
+ node_modules
12
+ backend/**/templates/
src/README.md ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags: [gradio-custom-component, SimpleTextbox, workflow, builder, editor]
3
+ title: gradio_workflowbuilder
4
+ short_description: workflow builder
5
+ colorFrom: blue
6
+ colorTo: yellow
7
+ sdk: gradio
8
+ pinned: false
9
+ app_file: space.py
10
+ ---
11
+
12
+ # `gradio_workflowbuilder`
13
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
14
+
15
+ workflow builder
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install gradio_workflowbuilder
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ import gradio as gr
27
+ from gradio_workflowbuilder import WorkflowBuilder
28
+ import json
29
+
30
+
31
+ def export_workflow(workflow_data):
32
+ """Export workflow as formatted JSON"""
33
+ if not workflow_data:
34
+ return "No workflow to export"
35
+ return json.dumps(workflow_data, indent=2)
36
+
37
+
38
+ # Create the main interface
39
+ with gr.Blocks(
40
+ title="🎨 Visual Workflow Builder",
41
+ theme=gr.themes.Soft(),
42
+ css="""
43
+ .main-container { max-width: 1600px; margin: 0 auto; }
44
+ .workflow-section { margin-bottom: 2rem; }
45
+ .button-row { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
46
+
47
+ .component-description {
48
+ padding: 24px;
49
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
50
+ border-radius: 12px;
51
+ border-left: 4px solid #3b82f6;
52
+ margin: 16px 0;
53
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
54
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
55
+ }
56
+
57
+ .component-description p {
58
+ margin: 10px 0;
59
+ line-height: 1.6;
60
+ color: #374151;
61
+ }
62
+
63
+ .base-description {
64
+ font-size: 17px;
65
+ font-weight: 600;
66
+ color: #1e293b;
67
+ }
68
+
69
+ .base-description strong {
70
+ color: #3b82f6;
71
+ font-weight: 700;
72
+ }
73
+
74
+ .feature-description {
75
+ font-size: 16px;
76
+ color: #1e293b;
77
+ font-weight: 500;
78
+ }
79
+
80
+ .customization-note {
81
+ font-size: 15px;
82
+ color: #64748b;
83
+ font-style: italic;
84
+ }
85
+
86
+ .sample-intro {
87
+ font-size: 15px;
88
+ color: #1e293b;
89
+ font-weight: 600;
90
+ margin-top: 16px;
91
+ border-top: 1px solid #e2e8f0;
92
+ padding-top: 16px;
93
+ }
94
+ """
95
+ ) as demo:
96
+
97
+ with gr.Column(elem_classes=["main-container"]):
98
+ gr.Markdown("""
99
+ # 🎨 Visual Workflow Builder
100
+
101
+ **Create sophisticated workflows with drag and drop simplicity.**
102
+ """)
103
+
104
+ # Simple description section with styling
105
+ gr.HTML("""
106
+ <div class="component-description">
107
+ <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
108
+ <p class="feature-description">Create custom workflows.</p>
109
+ <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
110
+ <p class="sample-intro">Here is a sample:</p>
111
+ </div>
112
+ """)
113
+
114
+ # Main workflow builder section
115
+ with gr.Column(elem_classes=["workflow-section"]):
116
+ workflow_builder = WorkflowBuilder(
117
+ label="🎨 Visual Workflow Designer",
118
+ info="Drag components from the sidebar → Connect nodes by dragging from output (right) to input (left) → Click nodes to edit properties"
119
+ )
120
+
121
+ # Export section below the workflow
122
+ gr.Markdown("## 💾 Export Workflow")
123
+
124
+ with gr.Row():
125
+ with gr.Column():
126
+ export_output = gr.Code(
127
+ language="json",
128
+ label="Workflow Configuration",
129
+ lines=10
130
+ )
131
+
132
+ # Action button
133
+ with gr.Row(elem_classes=["button-row"]):
134
+ export_btn = gr.Button("💾 Export JSON", variant="primary", size="lg")
135
+
136
+ # Instructions
137
+ with gr.Accordion("📖 How to Use", open=False):
138
+ gr.Markdown("""
139
+ ### 🚀 Getting Started
140
+
141
+ 1. **Add Components**: Drag items from the left sidebar onto the canvas
142
+ 2. **Connect Nodes**: Drag from the blue circle on the right of a node to the left circle of another
143
+ 3. **Edit Properties**: Click any node to see its editable properties on the right panel
144
+ 4. **Organize**: Drag nodes around to create a clean workflow layout
145
+ 5. **Delete**: Use the ✕ button on nodes or click the red circle on connections
146
+
147
+ ### 🎯 Component Types
148
+
149
+ - **📥 Inputs**: Text fields, file uploads, number inputs
150
+ - **⚙️ Processing**: Language models, text processors, conditionals
151
+ - **📤 Outputs**: Text displays, file exports, charts
152
+ - **🔧 Tools**: API calls, data transformers, validators
153
+
154
+ ### 💡 Pro Tips
155
+
156
+ - **Collapsible Panels**: Use the arrow buttons to hide/show sidebars
157
+ - **Live Updates**: Properties update in real-time as you edit
158
+ - **Export Options**: Get JSON config for your workflow
159
+ """)
160
+
161
+ # Event handlers
162
+ export_btn.click(
163
+ fn=export_workflow,
164
+ inputs=[workflow_builder],
165
+ outputs=[export_output]
166
+ )
167
+
168
+
169
+ if __name__ == "__main__":
170
+ demo.launch(
171
+ server_name="0.0.0.0",
172
+ show_error=True
173
+ )
174
+
175
+ ```
176
+
177
+ ## `WorkflowBuilder`
178
+
179
+ ### Initialization
180
+
181
+ <table>
182
+ <thead>
183
+ <tr>
184
+ <th align="left">name</th>
185
+ <th align="left" style="width: 25%;">type</th>
186
+ <th align="left">default</th>
187
+ <th align="left">description</th>
188
+ </tr>
189
+ </thead>
190
+ <tbody>
191
+ <tr>
192
+ <td align="left"><code>value</code></td>
193
+ <td align="left" style="width: 25%;">
194
+
195
+ ```python
196
+ typing.Optional[typing.Dict[str, typing.Any]][
197
+ typing.Dict[str, typing.Any][str, typing.Any], None
198
+ ]
199
+ ```
200
+
201
+ </td>
202
+ <td align="left"><code>None</code></td>
203
+ <td align="left">Default workflow data with nodes and edges</td>
204
+ </tr>
205
+
206
+ <tr>
207
+ <td align="left"><code>label</code></td>
208
+ <td align="left" style="width: 25%;">
209
+
210
+ ```python
211
+ typing.Optional[str][str, None]
212
+ ```
213
+
214
+ </td>
215
+ <td align="left"><code>None</code></td>
216
+ <td align="left">Component label</td>
217
+ </tr>
218
+
219
+ <tr>
220
+ <td align="left"><code>info</code></td>
221
+ <td align="left" style="width: 25%;">
222
+
223
+ ```python
224
+ typing.Optional[str][str, None]
225
+ ```
226
+
227
+ </td>
228
+ <td align="left"><code>None</code></td>
229
+ <td align="left">Additional component information</td>
230
+ </tr>
231
+
232
+ <tr>
233
+ <td align="left"><code>show_label</code></td>
234
+ <td align="left" style="width: 25%;">
235
+
236
+ ```python
237
+ typing.Optional[bool][bool, None]
238
+ ```
239
+
240
+ </td>
241
+ <td align="left"><code>None</code></td>
242
+ <td align="left">Whether to show the label</td>
243
+ </tr>
244
+
245
+ <tr>
246
+ <td align="left"><code>container</code></td>
247
+ <td align="left" style="width: 25%;">
248
+
249
+ ```python
250
+ bool
251
+ ```
252
+
253
+ </td>
254
+ <td align="left"><code>True</code></td>
255
+ <td align="left">Whether to use container styling</td>
256
+ </tr>
257
+
258
+ <tr>
259
+ <td align="left"><code>scale</code></td>
260
+ <td align="left" style="width: 25%;">
261
+
262
+ ```python
263
+ typing.Optional[int][int, None]
264
+ ```
265
+
266
+ </td>
267
+ <td align="left"><code>None</code></td>
268
+ <td align="left">Relative width scale</td>
269
+ </tr>
270
+
271
+ <tr>
272
+ <td align="left"><code>min_width</code></td>
273
+ <td align="left" style="width: 25%;">
274
+
275
+ ```python
276
+ int
277
+ ```
278
+
279
+ </td>
280
+ <td align="left"><code>160</code></td>
281
+ <td align="left">Minimum width in pixels</td>
282
+ </tr>
283
+
284
+ <tr>
285
+ <td align="left"><code>visible</code></td>
286
+ <td align="left" style="width: 25%;">
287
+
288
+ ```python
289
+ bool
290
+ ```
291
+
292
+ </td>
293
+ <td align="left"><code>True</code></td>
294
+ <td align="left">Whether component is visible</td>
295
+ </tr>
296
+
297
+ <tr>
298
+ <td align="left"><code>elem_id</code></td>
299
+ <td align="left" style="width: 25%;">
300
+
301
+ ```python
302
+ typing.Optional[str][str, None]
303
+ ```
304
+
305
+ </td>
306
+ <td align="left"><code>None</code></td>
307
+ <td align="left">HTML element ID</td>
308
+ </tr>
309
+
310
+ <tr>
311
+ <td align="left"><code>elem_classes</code></td>
312
+ <td align="left" style="width: 25%;">
313
+
314
+ ```python
315
+ typing.Optional[typing.List[str]][
316
+ typing.List[str][str], None
317
+ ]
318
+ ```
319
+
320
+ </td>
321
+ <td align="left"><code>None</code></td>
322
+ <td align="left">CSS classes</td>
323
+ </tr>
324
+
325
+ <tr>
326
+ <td align="left"><code>render</code></td>
327
+ <td align="left" style="width: 25%;">
328
+
329
+ ```python
330
+ bool
331
+ ```
332
+
333
+ </td>
334
+ <td align="left"><code>True</code></td>
335
+ <td align="left">Whether to render immediately</td>
336
+ </tr>
337
+ </tbody></table>
338
+
339
+
340
+ ### Events
341
+
342
+ | name | description |
343
+ |:-----|:------------|
344
+ | `change` | Triggered when the value of the WorkflowBuilder changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input. |
345
+ | `input` | This listener is triggered when the user changes the value of the WorkflowBuilder. |
346
+
347
+
348
+
349
+ ### User function
350
+
351
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
352
+
353
+ - When used as an Input, the component only impacts the input signature of the user function.
354
+ - When used as an output, the component only impacts the return signature of the user function.
355
+
356
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
357
+
358
+
359
+
360
+ ```python
361
+ def predict(
362
+ value: typing.Dict[str, typing.Any][str, typing.Any]
363
+ ) -> typing.Dict[str, typing.Any][str, typing.Any]:
364
+ return value
365
+ ```
366
+
src/backend/gradio_workflowbuilder/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+
2
+ from .workflowbuilder import WorkflowBuilder
3
+
4
+ __all__ = ['WorkflowBuilder']
src/backend/gradio_workflowbuilder/templates/component/index.js ADDED
The diff for this file is too large to render. See raw diff
 
src/backend/gradio_workflowbuilder/templates/component/style.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .workflow-builder.svelte-c4syt2.svelte-c4syt2{width:100%;height:700px;border:1px solid #e2e8f0;border-radius:12px;display:flex;flex-direction:column;background:#fff;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;overflow:hidden;box-shadow:0 4px 20px #00000014}.hide.svelte-c4syt2.svelte-c4syt2{display:none}.top-section.svelte-c4syt2.svelte-c4syt2{flex:1;display:flex;min-height:0}.sidebar.svelte-c4syt2.svelte-c4syt2{width:240px;min-width:240px;background:#f8fafc;border-right:1px solid #e2e8f0;display:flex;flex-direction:column;transition:all .3s cubic-bezier(.4,0,.2,1);position:relative}.sidebar.collapsed.svelte-c4syt2.svelte-c4syt2{width:48px;min-width:48px}.sidebar-header.svelte-c4syt2.svelte-c4syt2{padding:12px;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between;background:#fff;min-height:50px;box-sizing:border-box}.sidebar-header.svelte-c4syt2 h3.svelte-c4syt2{margin:0;font-size:15px;font-weight:600;color:#1e293b}.toggle-btn.svelte-c4syt2.svelte-c4syt2{background:#f1f5f9;border:1px solid #e2e8f0;border-radius:6px;padding:6px 8px;cursor:pointer;color:#64748b;font-size:14px;transition:all .2s;min-width:28px;height:28px;display:flex;align-items:center;justify-content:center;z-index:10;position:relative}.toggle-btn.svelte-c4syt2.svelte-c4syt2:hover{background:#e2e8f0;color:#475569}.sidebar-toggle.svelte-c4syt2.svelte-c4syt2{position:absolute;right:8px;top:50%;transform:translateY(-50%)}.sidebar-content.svelte-c4syt2.svelte-c4syt2{flex:1;overflow-y:auto;padding:12px}.category.svelte-c4syt2.svelte-c4syt2{margin-bottom:12px}.category-header.svelte-c4syt2.svelte-c4syt2{display:flex;align-items:center;padding:6px 0;font-weight:600;font-size:12px;color:#374151;border-bottom:1px solid #e5e7eb;margin-bottom:6px}.category-icon.svelte-c4syt2.svelte-c4syt2{margin-right:6px;font-size:14px}.component-item.svelte-c4syt2.svelte-c4syt2{display:flex;align-items:center;padding:6px 8px;margin-bottom:3px;background:#fff;border:1px solid #e5e7eb;border-radius:6px;cursor:grab;transition:all .2s ease;font-size:12px}.component-item.svelte-c4syt2.svelte-c4syt2:hover{background:#f8fafc;border-color:#cbd5e1;transform:translate(2px)}.component-item.svelte-c4syt2.svelte-c4syt2:active{cursor:grabbing}.component-icon.svelte-c4syt2.svelte-c4syt2{margin-right:6px;font-size:14px}.component-label.svelte-c4syt2.svelte-c4syt2{font-weight:500;color:#374151}.canvas-area.svelte-c4syt2.svelte-c4syt2{flex:1;display:flex;flex-direction:column;min-width:400px}.toolbar.svelte-c4syt2.svelte-c4syt2{height:50px;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between;padding:0 16px;background:#fff;box-shadow:0 1px 3px #0000000d}.workflow-name-input.svelte-c4syt2.svelte-c4syt2{font-size:16px;font-weight:600;color:#1e293b;border:none;background:transparent;outline:none;padding:4px 8px;border-radius:4px;transition:background .2s}.workflow-name-input.svelte-c4syt2.svelte-c4syt2:hover,.workflow-name-input.svelte-c4syt2.svelte-c4syt2:focus{background:#f1f5f9}.toolbar-center.svelte-c4syt2.svelte-c4syt2{display:flex;align-items:center}.zoom-controls.svelte-c4syt2.svelte-c4syt2{display:flex;align-items:center;gap:4px;background:#f1f5f9;padding:4px;border-radius:8px;border:1px solid #e2e8f0}.zoom-btn.svelte-c4syt2.svelte-c4syt2{background:#fff;border:none;width:28px;height:28px;border-radius:4px;cursor:pointer;font-weight:600;display:flex;align-items:center;justify-content:center;transition:all .2s;font-size:14px}.zoom-btn.svelte-c4syt2.svelte-c4syt2:hover{background:#e2e8f0}.zoom-btn.reset.svelte-c4syt2.svelte-c4syt2{font-size:12px}.zoom-level.svelte-c4syt2.svelte-c4syt2{font-size:12px;font-weight:600;color:#64748b;min-width:40px;text-align:center}.toolbar-right.svelte-c4syt2.svelte-c4syt2{display:flex;gap:12px;font-size:12px;align-items:center}.node-count.svelte-c4syt2.svelte-c4syt2,.edge-count.svelte-c4syt2.svelte-c4syt2{color:#64748b;background:#f1f5f9;padding:4px 8px;border-radius:12px;font-weight:500}.export-btn.svelte-c4syt2.svelte-c4syt2{background:#3b82f6;color:#fff;border:none;padding:6px 12px;border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;transition:all .2s;display:flex;align-items:center;gap:4px}.export-btn.svelte-c4syt2.svelte-c4syt2:hover{background:#2563eb;transform:translateY(-1px)}.canvas-container.svelte-c4syt2.svelte-c4syt2{flex:1;position:relative;overflow:hidden;background:#fafbfc;cursor:grab}.canvas-container.svelte-c4syt2.svelte-c4syt2:active{cursor:grabbing}.canvas.svelte-c4syt2.svelte-c4syt2{position:absolute;top:0;left:0;width:4000px;height:4000px;transform-origin:0 0}.grid-background.svelte-c4syt2.svelte-c4syt2{position:absolute;top:0;left:0;width:100%;height:100%;background-image:radial-gradient(circle,#e2e8f0 1px,transparent 1px);background-size:20px 20px;pointer-events:none;opacity:.6}.edges-layer.svelte-c4syt2.svelte-c4syt2{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1}.edge-delete.svelte-c4syt2.svelte-c4syt2,.edge-delete-text.svelte-c4syt2.svelte-c4syt2{pointer-events:all;cursor:pointer}.edge-delete-text.svelte-c4syt2.svelte-c4syt2{font-size:10px;fill:#fff;text-anchor:middle;-webkit-user-select:none;user-select:none}.edge-delete.svelte-c4syt2.svelte-c4syt2:hover{fill:#dc2626}.node.svelte-c4syt2.svelte-c4syt2{position:absolute;width:320px;min-height:160px;background:#fff;border:2px solid #e2e8f0;border-radius:10px;cursor:move;-webkit-user-select:none;user-select:none;z-index:2;transition:all .2s ease;box-shadow:0 2px 8px #0000001a;overflow:visible}.node.svelte-c4syt2.svelte-c4syt2:hover{box-shadow:0 4px 16px #00000026;transform:translateY(-1px)}.node.selected.svelte-c4syt2.svelte-c4syt2{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a,0 4px 16px #00000026}.node-header.svelte-c4syt2.svelte-c4syt2{display:flex;align-items:center;padding:12px 16px;color:#fff;font-weight:600;font-size:14px;border-radius:8px 8px 0 0;min-height:24px}.node-icon.svelte-c4syt2.svelte-c4syt2{margin-right:8px;font-size:16px;flex-shrink:0}.node-title.svelte-c4syt2.svelte-c4syt2{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.node-delete.svelte-c4syt2.svelte-c4syt2{background:#fff3;border:none;color:#fff;cursor:pointer;font-size:12px;padding:4px 6px;border-radius:4px;transition:all .2s;flex-shrink:0}.node-delete.svelte-c4syt2.svelte-c4syt2:hover{background:#ffffff4d}.node-content.svelte-c4syt2.svelte-c4syt2{padding:12px 16px;max-height:200px;overflow-y:auto;overflow-x:hidden}.node-property.svelte-c4syt2.svelte-c4syt2{display:flex;flex-direction:column;gap:4px;margin-bottom:12px;font-size:12px}.property-label.svelte-c4syt2.svelte-c4syt2{font-weight:600;color:#374151;font-size:11px;margin-bottom:2px}.property-input.svelte-c4syt2.svelte-c4syt2,.property-select.svelte-c4syt2.svelte-c4syt2{width:100%;padding:6px 8px;border:1px solid #d1d5db;border-radius:4px;font-size:11px;background:#fff;transition:all .2s;box-sizing:border-box;resize:vertical}.property-input.svelte-c4syt2.svelte-c4syt2:focus,.property-select.svelte-c4syt2.svelte-c4syt2:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 2px #3b82f61a}.property-input.svelte-c4syt2.svelte-c4syt2:hover,.property-select.svelte-c4syt2.svelte-c4syt2:hover{border-color:#9ca3af}.property-checkbox.svelte-c4syt2.svelte-c4syt2{display:flex;align-items:center;gap:6px;font-size:11px;color:#374151;cursor:pointer}.property-checkbox.svelte-c4syt2 input[type=checkbox].svelte-c4syt2{width:auto;margin:0;cursor:pointer}.node-status.svelte-c4syt2.svelte-c4syt2{font-size:12px;color:#64748b;text-align:center;padding:20px;font-style:italic}.connection-point.svelte-c4syt2.svelte-c4syt2{position:absolute;width:12px;height:12px;border-radius:50%;background:#3b82f6;border:2px solid white;cursor:crosshair;z-index:3;transition:all .2s ease;box-shadow:0 1px 3px #0000001a}.connection-point.input.svelte-c4syt2.svelte-c4syt2{left:-6px}.connection-point.output.svelte-c4syt2.svelte-c4syt2{right:-6px}.connection-point.svelte-c4syt2.svelte-c4syt2:hover{background:#2563eb;transform:scale(1.2);box-shadow:0 2px 6px #0003}.property-panel.svelte-c4syt2.svelte-c4syt2{width:280px;min-width:280px;background:#f8fafc;border-left:1px solid #e2e8f0;display:flex;flex-direction:column;transition:all .3s cubic-bezier(.4,0,.2,1);position:relative}.property-panel.collapsed.svelte-c4syt2.svelte-c4syt2{width:48px;min-width:48px}.property-header.svelte-c4syt2.svelte-c4syt2{padding:12px;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between;background:#fff;min-height:50px;box-sizing:border-box}.property-header.svelte-c4syt2 h3.svelte-c4syt2{margin:0;font-size:15px;font-weight:600;color:#1e293b}.property-toggle.svelte-c4syt2.svelte-c4syt2{position:absolute;left:8px;top:50%;transform:translateY(-50%)}.property-content.svelte-c4syt2.svelte-c4syt2{flex:1;overflow-y:auto;padding:16px}.property-node-info.svelte-c4syt2.svelte-c4syt2{margin-bottom:20px;padding:12px;background:#fff;border-radius:8px;border:1px solid #e2e8f0}.property-node-info.svelte-c4syt2 h4.svelte-c4syt2{margin:0 0 4px;font-size:16px;color:#1e293b}.property-node-type.svelte-c4syt2.svelte-c4syt2{margin:0;font-size:11px;color:#64748b;text-transform:uppercase;font-weight:600}.property-field.svelte-c4syt2.svelte-c4syt2{margin-bottom:16px}.property-field.svelte-c4syt2 label.svelte-c4syt2{display:block;margin-bottom:6px;font-size:13px;font-weight:600;color:#374151}.field-help.svelte-c4syt2.svelte-c4syt2{display:block;margin-bottom:4px;font-size:11px;color:#64748b;font-style:italic}.property-field.svelte-c4syt2 input.svelte-c4syt2,.property-field.svelte-c4syt2 select.svelte-c4syt2,.property-field.svelte-c4syt2 textarea.svelte-c4syt2{width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;background:#fff;transition:border-color .2s;box-sizing:border-box}.property-field.svelte-c4syt2 input.svelte-c4syt2:focus,.property-field.svelte-c4syt2 select.svelte-c4syt2:focus,.property-field.svelte-c4syt2 textarea.svelte-c4syt2:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.checkbox-label.svelte-c4syt2.svelte-c4syt2{display:flex!important;align-items:center;margin-bottom:0!important;cursor:pointer}.checkbox-label.svelte-c4syt2 input[type=checkbox].svelte-c4syt2{width:auto!important;margin-right:8px!important}.property-empty.svelte-c4syt2.svelte-c4syt2{text-align:center;padding:40px 16px;color:#64748b}.empty-icon.svelte-c4syt2.svelte-c4syt2{font-size:32px;margin-bottom:12px;opacity:.5}.property-empty.svelte-c4syt2 p.svelte-c4syt2{margin:0 0 6px;font-size:14px;font-weight:500}.property-empty.svelte-c4syt2 small.svelte-c4syt2{font-size:12px;opacity:.7}.clear-btn.svelte-c4syt2.svelte-c4syt2{background:#ef4444;color:#fff;border:none;padding:6px 12px;border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;transition:all .2s;display:flex;align-items:center;gap:4px}.clear-btn.svelte-c4syt2.svelte-c4syt2:hover{background:#dc2626;transform:translateY(-1px)}
src/backend/gradio_workflowbuilder/templates/example/index.js ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const {
2
+ SvelteComponent: h,
3
+ add_iframe_resize_listener: g,
4
+ add_render_callback: v,
5
+ append_hydration: y,
6
+ attr: b,
7
+ binding_callbacks: m,
8
+ children: w,
9
+ claim_element: z,
10
+ claim_text: k,
11
+ detach: c,
12
+ element: p,
13
+ init: E,
14
+ insert_hydration: S,
15
+ noop: o,
16
+ safe_not_equal: q,
17
+ set_data: C,
18
+ text: D,
19
+ toggle_class: _
20
+ } = window.__gradio__svelte__internal, { onMount: I } = window.__gradio__svelte__internal;
21
+ function M(t) {
22
+ let e, i = (
23
+ /*value*/
24
+ (t[0] ? (
25
+ /*value*/
26
+ t[0]
27
+ ) : "") + ""
28
+ ), s, d;
29
+ return {
30
+ c() {
31
+ e = p("div"), s = D(i), this.h();
32
+ },
33
+ l(l) {
34
+ e = z(l, "DIV", { class: !0 });
35
+ var n = w(e);
36
+ s = k(n, i), n.forEach(c), this.h();
37
+ },
38
+ h() {
39
+ b(e, "class", "svelte-84cxb8"), v(() => (
40
+ /*div_elementresize_handler*/
41
+ t[5].call(e)
42
+ )), _(
43
+ e,
44
+ "table",
45
+ /*type*/
46
+ t[1] === "table"
47
+ ), _(
48
+ e,
49
+ "gallery",
50
+ /*type*/
51
+ t[1] === "gallery"
52
+ ), _(
53
+ e,
54
+ "selected",
55
+ /*selected*/
56
+ t[2]
57
+ );
58
+ },
59
+ m(l, n) {
60
+ S(l, e, n), y(e, s), d = g(
61
+ e,
62
+ /*div_elementresize_handler*/
63
+ t[5].bind(e)
64
+ ), t[6](e);
65
+ },
66
+ p(l, [n]) {
67
+ n & /*value*/
68
+ 1 && i !== (i = /*value*/
69
+ (l[0] ? (
70
+ /*value*/
71
+ l[0]
72
+ ) : "") + "") && C(s, i), n & /*type*/
73
+ 2 && _(
74
+ e,
75
+ "table",
76
+ /*type*/
77
+ l[1] === "table"
78
+ ), n & /*type*/
79
+ 2 && _(
80
+ e,
81
+ "gallery",
82
+ /*type*/
83
+ l[1] === "gallery"
84
+ ), n & /*selected*/
85
+ 4 && _(
86
+ e,
87
+ "selected",
88
+ /*selected*/
89
+ l[2]
90
+ );
91
+ },
92
+ i: o,
93
+ o,
94
+ d(l) {
95
+ l && c(e), d(), t[6](null);
96
+ }
97
+ };
98
+ }
99
+ function P(t, e) {
100
+ t.style.setProperty("--local-text-width", `${e && e < 150 ? e : 200}px`), t.style.whiteSpace = "unset";
101
+ }
102
+ function V(t, e, i) {
103
+ let { value: s } = e, { type: d } = e, { selected: l = !1 } = e, n, r;
104
+ I(() => {
105
+ P(r, n);
106
+ });
107
+ function u() {
108
+ n = this.clientWidth, i(3, n);
109
+ }
110
+ function f(a) {
111
+ m[a ? "unshift" : "push"](() => {
112
+ r = a, i(4, r);
113
+ });
114
+ }
115
+ return t.$$set = (a) => {
116
+ "value" in a && i(0, s = a.value), "type" in a && i(1, d = a.type), "selected" in a && i(2, l = a.selected);
117
+ }, [s, d, l, n, r, u, f];
118
+ }
119
+ class W extends h {
120
+ constructor(e) {
121
+ super(), E(this, e, V, M, q, { value: 0, type: 1, selected: 2 });
122
+ }
123
+ }
124
+ export {
125
+ W as default
126
+ };
src/backend/gradio_workflowbuilder/templates/example/style.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .gallery.svelte-84cxb8{padding:var(--size-1) var(--size-2)}div.svelte-84cxb8{overflow:hidden;min-width:var(--local-text-width);white-space:nowrap}
src/backend/gradio_workflowbuilder/workflowbuilder.py ADDED
@@ -0,0 +1,562 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+ from typing import Any, Dict, List, Optional, Union, Callable
3
+ import json
4
+ from gradio.components import Component
5
+ from gradio.events import Events
6
+
7
+
8
+ class WorkflowBuilder(Component):
9
+ """
10
+ Professional Workflow Builder component with support for 25+ node types
11
+ inspired by n8n and Langflow for AI agent development and MCP integration.
12
+ """
13
+
14
+ EVENTS = [Events.change, Events.input]
15
+
16
+ def __init__(
17
+ self,
18
+ value: Optional[Dict[str, Any]] = None,
19
+ label: Optional[str] = None,
20
+ info: Optional[str] = None,
21
+ show_label: Optional[bool] = None,
22
+ container: bool = True,
23
+ scale: Optional[int] = None,
24
+ min_width: int = 160,
25
+ visible: bool = True,
26
+ elem_id: Optional[str] = None,
27
+ elem_classes: Optional[List[str]] = None,
28
+ render: bool = True,
29
+ **kwargs,
30
+ ):
31
+ """
32
+ Parameters:
33
+ value: Default workflow data with nodes and edges
34
+ label: Component label
35
+ info: Additional component information
36
+ show_label: Whether to show the label
37
+ container: Whether to use container styling
38
+ scale: Relative width scale
39
+ min_width: Minimum width in pixels
40
+ visible: Whether component is visible
41
+ elem_id: HTML element ID
42
+ elem_classes: CSS classes
43
+ render: Whether to render immediately
44
+ """
45
+
46
+ # Initialize with empty workflow if no value provided
47
+ if value is None:
48
+ value = {"nodes": [], "edges": []}
49
+
50
+ # Validate the workflow data
51
+ if not isinstance(value, dict):
52
+ raise ValueError("Workflow value must be a dictionary")
53
+
54
+ if "nodes" not in value:
55
+ value["nodes"] = []
56
+ if "edges" not in value:
57
+ value["edges"] = []
58
+
59
+ super().__init__(
60
+ label=label,
61
+ info=info,
62
+ show_label=show_label,
63
+ container=container,
64
+ scale=scale,
65
+ min_width=min_width,
66
+ visible=visible,
67
+ elem_id=elem_id,
68
+ elem_classes=elem_classes,
69
+ render=render,
70
+ value=value,
71
+ **kwargs,
72
+ )
73
+
74
+ def preprocess(self, payload: Dict[str, Any]) -> Dict[str, Any]:
75
+ """
76
+ Process workflow data from frontend
77
+ """
78
+ if payload is None:
79
+ return {"nodes": [], "edges": []}
80
+
81
+ # Validate and clean the workflow data
82
+ workflow = self._validate_workflow(payload)
83
+ return workflow
84
+
85
+ def postprocess(self, value: Dict[str, Any]) -> Dict[str, Any]:
86
+ """
87
+ Process workflow data for frontend
88
+ """
89
+ if value is None:
90
+ return {"nodes": [], "edges": []}
91
+
92
+ # Ensure proper structure
93
+ if not isinstance(value, dict):
94
+ return {"nodes": [], "edges": []}
95
+
96
+ return {
97
+ "nodes": value.get("nodes", []),
98
+ "edges": value.get("edges", [])
99
+ }
100
+
101
+ def _validate_workflow(self, workflow: Dict[str, Any]) -> Dict[str, Any]:
102
+ """
103
+ Validate workflow structure and node configurations
104
+ """
105
+ if not isinstance(workflow, dict):
106
+ return {"nodes": [], "edges": []}
107
+
108
+ nodes = workflow.get("nodes", [])
109
+ edges = workflow.get("edges", [])
110
+
111
+ # Validate each node
112
+ validated_nodes = []
113
+ for node in nodes:
114
+ if self._validate_node(node):
115
+ validated_nodes.append(node)
116
+
117
+ # Validate each edge
118
+ validated_edges = []
119
+ node_ids = {node["id"] for node in validated_nodes}
120
+ for edge in edges:
121
+ if self._validate_edge(edge, node_ids):
122
+ validated_edges.append(edge)
123
+
124
+ return {
125
+ "nodes": validated_nodes,
126
+ "edges": validated_edges
127
+ }
128
+
129
+ def _validate_node(self, node: Dict[str, Any]) -> bool:
130
+ """
131
+ Validate individual node structure and properties
132
+ """
133
+ required_fields = ["id", "type", "position", "data"]
134
+
135
+ # Check required fields
136
+ if not all(field in node for field in required_fields):
137
+ return False
138
+
139
+ # Validate node type
140
+ if not self._is_valid_node_type(node["type"]):
141
+ return False
142
+
143
+ # Validate position
144
+ position = node["position"]
145
+ if not isinstance(position, dict) or "x" not in position or "y" not in position:
146
+ return False
147
+
148
+ # Validate node data based on type
149
+ return self._validate_node_data(node["type"], node["data"])
150
+
151
+ def _validate_edge(self, edge: Dict[str, Any], valid_node_ids: set) -> bool:
152
+ """
153
+ Validate edge connections
154
+ """
155
+ required_fields = ["id", "source", "target"]
156
+
157
+ if not all(field in edge for field in required_fields):
158
+ return False
159
+
160
+ # Check if source and target nodes exist
161
+ return (edge["source"] in valid_node_ids and
162
+ edge["target"] in valid_node_ids)
163
+
164
+ def _is_valid_node_type(self, node_type: str) -> bool:
165
+ """
166
+ Check if node type is supported
167
+ """
168
+ # All the node types from your frontend
169
+ supported_types = {
170
+ # Input/Output Nodes
171
+ "ChatInput", "ChatOutput", "Input", "Output",
172
+
173
+ # AI & Language Models
174
+ "OpenAIModel", "ChatModel", "Prompt", "HFTextGeneration",
175
+
176
+ # API & Web
177
+ "APIRequest", "WebSearch",
178
+
179
+ # Data Processing
180
+ "ExecutePython", "ConditionalLogic", "Wait",
181
+
182
+ # RAG & Knowledge
183
+ "KnowledgeBase", "RAGQuery",
184
+
185
+ # Speech & Vision
186
+ "HFSpeechToText", "HFTextToSpeech", "HFVisionModel",
187
+
188
+ # Image Generation
189
+ "HFImageGeneration", "NebiusImage",
190
+
191
+ # MCP Integration
192
+ "MCPConnection", "MCPAgent",
193
+
194
+ # Legacy types (for backward compatibility)
195
+ "textInput", "fileInput", "numberInput", "llm", "textProcessor",
196
+ "conditional", "textOutput", "fileOutput", "chartOutput",
197
+ "apiCall", "dataTransform", "webhook", "schedule", "manualTrigger",
198
+ "emailTrigger", "httpRequest", "googleSheets", "database", "csvFile",
199
+ "openaiChat", "claudeChat", "huggingFace", "textEmbedding",
200
+ "codeNode", "functionNode", "setNode", "jsonParse",
201
+ "ifCondition", "switchNode", "merge", "waitNode",
202
+ "email", "slack", "discord", "telegram",
203
+ "fileUpload", "awsS3", "googleDrive", "ftp",
204
+ "dateTime", "crypto", "validator", "regex"
205
+ }
206
+
207
+ return node_type in supported_types
208
+
209
+ def _validate_node_data(self, node_type: str, data: Dict[str, Any]) -> bool:
210
+ """
211
+ Validate node data based on node type
212
+ """
213
+ if not isinstance(data, dict):
214
+ return False
215
+
216
+ # Define required fields for each node type
217
+ required_fields = {
218
+ # Input/Output Nodes
219
+ "ChatInput": ["display_name", "template"],
220
+ "ChatOutput": ["display_name", "template"],
221
+ "Input": ["display_name", "template"],
222
+ "Output": ["display_name", "template"],
223
+
224
+ # AI & Language Models
225
+ "OpenAIModel": ["display_name", "template"],
226
+ "ChatModel": ["display_name", "template"],
227
+ "Prompt": ["display_name", "template"],
228
+ "HFTextGeneration": ["display_name", "template"],
229
+
230
+ # API & Web
231
+ "APIRequest": ["display_name", "template"],
232
+ "WebSearch": ["display_name", "template"],
233
+
234
+ # Data Processing
235
+ "ExecutePython": ["display_name", "template"],
236
+ "ConditionalLogic": ["display_name", "template"],
237
+ "Wait": ["display_name", "template"],
238
+
239
+ # RAG & Knowledge
240
+ "KnowledgeBase": ["display_name", "template"],
241
+ "RAGQuery": ["display_name", "template"],
242
+
243
+ # Speech & Vision
244
+ "HFSpeechToText": ["display_name", "template"],
245
+ "HFTextToSpeech": ["display_name", "template"],
246
+ "HFVisionModel": ["display_name", "template"],
247
+
248
+ # Image Generation
249
+ "HFImageGeneration": ["display_name", "template"],
250
+ "NebiusImage": ["display_name", "template"],
251
+
252
+ # MCP Integration
253
+ "MCPConnection": ["display_name", "template"],
254
+ "MCPAgent": ["display_name", "template"],
255
+
256
+ # Legacy types
257
+ "webhook": ["method", "path"],
258
+ "httpRequest": ["method", "url"],
259
+ "openaiChat": ["model"],
260
+ "claudeChat": ["model"],
261
+ "codeNode": ["language", "code"],
262
+ "ifCondition": ["conditions"],
263
+ "email": ["fromEmail", "toEmail", "subject"],
264
+ "awsS3": ["operation", "bucketName"]
265
+ }
266
+
267
+ # Check required fields for this node type
268
+ if node_type in required_fields:
269
+ required = required_fields[node_type]
270
+ if not all(field in data for field in required):
271
+ return False
272
+
273
+ return True
274
+
275
+ def api_info(self) -> Dict[str, Any]:
276
+ """
277
+ API information for the component
278
+ """
279
+ return {
280
+ "info": {
281
+ "type": "object",
282
+ "properties": {
283
+ "nodes": {
284
+ "type": "array",
285
+ "items": {
286
+ "type": "object",
287
+ "properties": {
288
+ "id": {"type": "string"},
289
+ "type": {"type": "string"},
290
+ "position": {
291
+ "type": "object",
292
+ "properties": {
293
+ "x": {"type": "number"},
294
+ "y": {"type": "number"}
295
+ }
296
+ },
297
+ "data": {"type": "object"}
298
+ }
299
+ }
300
+ },
301
+ "edges": {
302
+ "type": "array",
303
+ "items": {
304
+ "type": "object",
305
+ "properties": {
306
+ "id": {"type": "string"},
307
+ "source": {"type": "string"},
308
+ "target": {"type": "string"}
309
+ }
310
+ }
311
+ }
312
+ }
313
+ }
314
+ }
315
+
316
+ def example_payload(self) -> Dict[str, Any]:
317
+ """
318
+ Example payload for the component
319
+ """
320
+ return {
321
+ "nodes": [
322
+ {
323
+ "id": "ChatInput-1",
324
+ "type": "ChatInput",
325
+ "position": {"x": 100, "y": 100},
326
+ "data": {
327
+ "display_name": "User's Question",
328
+ "template": {
329
+ "input_value": {
330
+ "display_name": "Input",
331
+ "type": "string",
332
+ "value": "What is the capital of France?",
333
+ "is_handle": True
334
+ }
335
+ }
336
+ }
337
+ },
338
+ {
339
+ "id": "Prompt-1",
340
+ "type": "Prompt",
341
+ "position": {"x": 300, "y": 100},
342
+ "data": {
343
+ "display_name": "System Prompt",
344
+ "template": {
345
+ "prompt_template": {
346
+ "display_name": "Template",
347
+ "type": "string",
348
+ "value": "You are a helpful geography expert. The user asked: {input_value}",
349
+ "is_handle": True
350
+ }
351
+ }
352
+ }
353
+ },
354
+ {
355
+ "id": "OpenAI-1",
356
+ "type": "OpenAIModel",
357
+ "position": {"x": 500, "y": 100},
358
+ "data": {
359
+ "display_name": "OpenAI gpt-4o-mini",
360
+ "template": {
361
+ "model": {
362
+ "display_name": "Model",
363
+ "type": "options",
364
+ "options": ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"],
365
+ "value": "gpt-4o-mini"
366
+ },
367
+ "api_key": {
368
+ "display_name": "API Key",
369
+ "type": "SecretStr",
370
+ "required": True,
371
+ "env_var": "OPENAI_API_KEY"
372
+ },
373
+ "prompt": {
374
+ "display_name": "Prompt",
375
+ "type": "string",
376
+ "is_handle": True
377
+ }
378
+ }
379
+ }
380
+ },
381
+ {
382
+ "id": "ChatOutput-1",
383
+ "type": "ChatOutput",
384
+ "position": {"x": 700, "y": 100},
385
+ "data": {
386
+ "display_name": "Final Answer",
387
+ "template": {
388
+ "response": {
389
+ "display_name": "Response",
390
+ "type": "string",
391
+ "is_handle": True
392
+ }
393
+ }
394
+ }
395
+ }
396
+ ],
397
+ "edges": [
398
+ {
399
+ "id": "e1",
400
+ "source": "ChatInput-1",
401
+ "source_handle": "input_value",
402
+ "target": "Prompt-1",
403
+ "target_handle": "prompt_template"
404
+ },
405
+ {
406
+ "id": "e2",
407
+ "source": "Prompt-1",
408
+ "source_handle": "prompt_template",
409
+ "target": "OpenAI-1",
410
+ "target_handle": "prompt"
411
+ },
412
+ {
413
+ "id": "e3",
414
+ "source": "OpenAI-1",
415
+ "source_handle": "response",
416
+ "target": "ChatOutput-1",
417
+ "target_handle": "response"
418
+ }
419
+ ]
420
+ }
421
+
422
+ def example_value(self) -> Dict[str, Any]:
423
+ """
424
+ Example value for the component
425
+ """
426
+ return self.example_payload()
427
+
428
+
429
+ # Utility functions for workflow analysis and execution
430
+ class WorkflowAnalyzer:
431
+ """
432
+ Analyze workflow configurations and provide insights
433
+ """
434
+
435
+ @staticmethod
436
+ def analyze_workflow(workflow: Dict[str, Any]) -> Dict[str, Any]:
437
+ """
438
+ Provide detailed analysis of a workflow
439
+ """
440
+ nodes = workflow.get("nodes", [])
441
+ edges = workflow.get("edges", [])
442
+
443
+ # Count node types
444
+ node_types = {}
445
+ for node in nodes:
446
+ node_type = node.get("type", "unknown")
447
+ node_types[node_type] = node_types.get(node_type, 0) + 1
448
+
449
+ # Analyze workflow complexity
450
+ complexity = "Simple"
451
+ if len(nodes) > 10:
452
+ complexity = "Complex"
453
+ elif len(nodes) > 5:
454
+ complexity = "Medium"
455
+
456
+ # Check for potential issues
457
+ issues = []
458
+
459
+ # Check for disconnected nodes
460
+ connected_nodes = set()
461
+ for edge in edges:
462
+ connected_nodes.add(edge["source"])
463
+ connected_nodes.add(edge["target"])
464
+
465
+ disconnected = [node["id"] for node in nodes if node["id"] not in connected_nodes]
466
+ if disconnected:
467
+ issues.append(f"Disconnected nodes: {', '.join(disconnected)}")
468
+
469
+ # Check for missing required fields and API keys
470
+ for node in nodes:
471
+ node_type = node.get("type")
472
+ data = node.get("data", {})
473
+
474
+ # Check for required API keys
475
+ if node_type == "OpenAIModel" and not data.get("template", {}).get("api_key", {}).get("value"):
476
+ issues.append(f"Node {node['id']} missing OpenAI API key")
477
+ elif node_type == "ChatModel" and not data.get("template", {}).get("api_key", {}).get("value"):
478
+ issues.append(f"Node {node['id']} missing API key")
479
+ elif node_type == "NebiusImage" and not data.get("template", {}).get("api_key", {}).get("value"):
480
+ issues.append(f"Node {node['id']} missing Nebius API key")
481
+
482
+ # Check for required model configurations
483
+ if node_type in ["OpenAIModel", "ChatModel", "HFTextGeneration"] and not data.get("template", {}).get("model", {}).get("value"):
484
+ issues.append(f"Node {node['id']} missing model configuration")
485
+
486
+ # Check for required templates
487
+ if node_type in ["Prompt", "ChatInput", "ChatOutput"] and not data.get("template"):
488
+ issues.append(f"Node {node['id']} missing template configuration")
489
+
490
+ # Analyze node categories
491
+ input_nodes = [n for n in nodes if n.get("type") in ["ChatInput", "Input"]]
492
+ processing_nodes = [n for n in nodes if n.get("type") in [
493
+ "OpenAIModel", "ChatModel", "Prompt", "HFTextGeneration",
494
+ "ExecutePython", "ConditionalLogic", "Wait", "APIRequest",
495
+ "WebSearch", "KnowledgeBase", "RAGQuery"
496
+ ]]
497
+ output_nodes = [n for n in nodes if n.get("type") in ["ChatOutput", "Output"]]
498
+ ai_nodes = [n for n in nodes if n.get("type") in [
499
+ "OpenAIModel", "ChatModel", "HFTextGeneration", "HFImageGeneration",
500
+ "NebiusImage", "HFSpeechToText", "HFTextToSpeech", "HFVisionModel"
501
+ ]]
502
+
503
+ return {
504
+ "total_nodes": len(nodes),
505
+ "total_edges": len(edges),
506
+ "node_types": node_types,
507
+ "complexity": complexity,
508
+ "issues": issues,
509
+ "is_valid": len(issues) == 0,
510
+ "categories": {
511
+ "input_nodes": len(input_nodes),
512
+ "processing_nodes": len(processing_nodes),
513
+ "output_nodes": len(output_nodes),
514
+ "ai_nodes": len(ai_nodes)
515
+ }
516
+ }
517
+
518
+ @staticmethod
519
+ def validate_for_execution(workflow: Dict[str, Any]) -> Dict[str, Any]:
520
+ """
521
+ Validate if workflow is ready for execution
522
+ """
523
+ analysis = WorkflowAnalyzer.analyze_workflow(workflow)
524
+
525
+ # Additional execution-specific checks
526
+ nodes = workflow.get("nodes", [])
527
+
528
+ # Check for entry points (input nodes)
529
+ input_types = {"ChatInput", "Input"}
530
+ inputs = [n for n in nodes if n.get("type") in input_types]
531
+
532
+ if not inputs:
533
+ analysis["issues"].append("No input nodes found - workflow needs an entry point")
534
+
535
+ # Check for output nodes
536
+ output_types = {"ChatOutput", "Output"}
537
+ outputs = [n for n in nodes if n.get("type") in output_types]
538
+
539
+ if not outputs:
540
+ analysis["issues"].append("No output nodes found - workflow needs an exit point")
541
+
542
+ # Check for required environment variables
543
+ env_vars = set()
544
+ for node in nodes:
545
+ data = node.get("data", {})
546
+ template = data.get("template", {})
547
+ for field in template.values():
548
+ if isinstance(field, dict) and field.get("type") == "SecretStr":
549
+ env_var = field.get("env_var")
550
+ if env_var:
551
+ env_vars.add(env_var)
552
+
553
+ if env_vars:
554
+ analysis["required_env_vars"] = list(env_vars)
555
+
556
+ analysis["is_executable"] = len(analysis["issues"]) == 0
557
+
558
+ return analysis
559
+
560
+
561
+ # Export the main component
562
+ __all__ = ["WorkflowBuilder", "WorkflowAnalyzer"]
src/demo/__init__.py ADDED
File without changes
src/demo/app.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from gradio_workflowbuilder import WorkflowBuilder
3
+ import json
4
+
5
+
6
+ def export_workflow(workflow_data):
7
+ """Export workflow as formatted JSON"""
8
+ if not workflow_data:
9
+ return "No workflow to export"
10
+ return json.dumps(workflow_data, indent=2)
11
+
12
+
13
+ # Create the main interface
14
+ with gr.Blocks(
15
+ title="🎨 Visual Workflow Builder",
16
+ theme=gr.themes.Soft(),
17
+ css="""
18
+ .main-container { max-width: 1600px; margin: 0 auto; }
19
+ .workflow-section { margin-bottom: 2rem; }
20
+ .button-row { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
21
+
22
+ .component-description {
23
+ padding: 24px;
24
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
25
+ border-radius: 12px;
26
+ border-left: 4px solid #3b82f6;
27
+ margin: 16px 0;
28
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
29
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
30
+ }
31
+
32
+ .component-description p {
33
+ margin: 10px 0;
34
+ line-height: 1.6;
35
+ color: #374151;
36
+ }
37
+
38
+ .base-description {
39
+ font-size: 17px;
40
+ font-weight: 600;
41
+ color: #1e293b;
42
+ }
43
+
44
+ .base-description strong {
45
+ color: #3b82f6;
46
+ font-weight: 700;
47
+ }
48
+
49
+ .feature-description {
50
+ font-size: 16px;
51
+ color: #1e293b;
52
+ font-weight: 500;
53
+ }
54
+
55
+ .customization-note {
56
+ font-size: 15px;
57
+ color: #64748b;
58
+ font-style: italic;
59
+ }
60
+
61
+ .sample-intro {
62
+ font-size: 15px;
63
+ color: #1e293b;
64
+ font-weight: 600;
65
+ margin-top: 16px;
66
+ border-top: 1px solid #e2e8f0;
67
+ padding-top: 16px;
68
+ }
69
+ """
70
+ ) as demo:
71
+
72
+ with gr.Column(elem_classes=["main-container"]):
73
+ gr.Markdown("""
74
+ # 🎨 Visual Workflow Builder
75
+
76
+ **Create sophisticated workflows with drag and drop simplicity.**
77
+ """)
78
+
79
+ # Simple description section with styling
80
+ gr.HTML("""
81
+ <div class="component-description">
82
+ <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
83
+ <p class="feature-description">Create custom workflows.</p>
84
+ <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
85
+ <p class="sample-intro">Here is a sample:</p>
86
+ </div>
87
+ """)
88
+
89
+ # Main workflow builder section
90
+ with gr.Column(elem_classes=["workflow-section"]):
91
+ workflow_builder = WorkflowBuilder(
92
+ label="🎨 Visual Workflow Designer",
93
+ info="Drag components from the sidebar → Connect nodes by dragging from output (right) to input (left) → Click nodes to edit properties"
94
+ )
95
+
96
+ # Export section below the workflow
97
+ gr.Markdown("## 💾 Export Workflow")
98
+
99
+ with gr.Row():
100
+ with gr.Column():
101
+ export_output = gr.Code(
102
+ language="json",
103
+ label="Workflow Configuration",
104
+ lines=10
105
+ )
106
+
107
+ # Action button
108
+ with gr.Row(elem_classes=["button-row"]):
109
+ export_btn = gr.Button("💾 Export JSON", variant="primary", size="lg")
110
+
111
+ # Instructions
112
+ with gr.Accordion("📖 How to Use", open=False):
113
+ gr.Markdown("""
114
+ ### 🚀 Getting Started
115
+
116
+ 1. **Add Components**: Drag items from the left sidebar onto the canvas
117
+ 2. **Connect Nodes**: Drag from the blue circle on the right of a node to the left circle of another
118
+ 3. **Edit Properties**: Click any node to see its editable properties on the right panel
119
+ 4. **Organize**: Drag nodes around to create a clean workflow layout
120
+ 5. **Delete**: Use the ✕ button on nodes or click the red circle on connections
121
+
122
+ ### 🎯 Component Types
123
+
124
+ - **📥 Inputs**: Text fields, file uploads, number inputs
125
+ - **⚙️ Processing**: Language models, text processors, conditionals
126
+ - **📤 Outputs**: Text displays, file exports, charts
127
+ - **🔧 Tools**: API calls, data transformers, validators
128
+
129
+ ### 💡 Pro Tips
130
+
131
+ - **Collapsible Panels**: Use the arrow buttons to hide/show sidebars
132
+ - **Live Updates**: Properties update in real-time as you edit
133
+ - **Export Options**: Get JSON config for your workflow
134
+ """)
135
+
136
+ # Event handlers
137
+ export_btn.click(
138
+ fn=export_workflow,
139
+ inputs=[workflow_builder],
140
+ outputs=[export_output]
141
+ )
142
+
143
+
144
+ if __name__ == "__main__":
145
+ demo.launch(
146
+ server_name="0.0.0.0",
147
+ show_error=True
148
+ )
src/demo/css.css ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ font-family: Inter;
3
+ font-size: 16px;
4
+ font-weight: 400;
5
+ line-height: 1.5;
6
+ -webkit-text-size-adjust: 100%;
7
+ background: #fff;
8
+ color: #323232;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ text-rendering: optimizeLegibility;
12
+ }
13
+
14
+ :root {
15
+ --space: 1;
16
+ --vspace: calc(var(--space) * 1rem);
17
+ --vspace-0: calc(3 * var(--space) * 1rem);
18
+ --vspace-1: calc(2 * var(--space) * 1rem);
19
+ --vspace-2: calc(1.5 * var(--space) * 1rem);
20
+ --vspace-3: calc(0.5 * var(--space) * 1rem);
21
+ }
22
+
23
+ .app {
24
+ max-width: 748px !important;
25
+ }
26
+
27
+ .prose p {
28
+ margin: var(--vspace) 0;
29
+ line-height: var(--vspace * 2);
30
+ font-size: 1rem;
31
+ }
32
+
33
+ code {
34
+ font-family: "Inconsolata", sans-serif;
35
+ font-size: 16px;
36
+ }
37
+
38
+ h1,
39
+ h1 code {
40
+ font-weight: 400;
41
+ line-height: calc(2.5 / var(--space) * var(--vspace));
42
+ }
43
+
44
+ h1 code {
45
+ background: none;
46
+ border: none;
47
+ letter-spacing: 0.05em;
48
+ padding-bottom: 5px;
49
+ position: relative;
50
+ padding: 0;
51
+ }
52
+
53
+ h2 {
54
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
55
+ line-height: 1em;
56
+ }
57
+
58
+ h3,
59
+ h3 code {
60
+ margin: var(--vspace-1) 0 var(--vspace-2) 0;
61
+ line-height: 1em;
62
+ }
63
+
64
+ h4,
65
+ h5,
66
+ h6 {
67
+ margin: var(--vspace-3) 0 var(--vspace-3) 0;
68
+ line-height: var(--vspace);
69
+ }
70
+
71
+ .bigtitle,
72
+ h1,
73
+ h1 code {
74
+ font-size: calc(8px * 4.5);
75
+ word-break: break-word;
76
+ }
77
+
78
+ .title,
79
+ h2,
80
+ h2 code {
81
+ font-size: calc(8px * 3.375);
82
+ font-weight: lighter;
83
+ word-break: break-word;
84
+ border: none;
85
+ background: none;
86
+ }
87
+
88
+ .subheading1,
89
+ h3,
90
+ h3 code {
91
+ font-size: calc(8px * 1.8);
92
+ font-weight: 600;
93
+ border: none;
94
+ background: none;
95
+ letter-spacing: 0.1em;
96
+ text-transform: uppercase;
97
+ }
98
+
99
+ h2 code {
100
+ padding: 0;
101
+ position: relative;
102
+ letter-spacing: 0.05em;
103
+ }
104
+
105
+ blockquote {
106
+ font-size: calc(8px * 1.1667);
107
+ font-style: italic;
108
+ line-height: calc(1.1667 * var(--vspace));
109
+ margin: var(--vspace-2) var(--vspace-2);
110
+ }
111
+
112
+ .subheading2,
113
+ h4 {
114
+ font-size: calc(8px * 1.4292);
115
+ text-transform: uppercase;
116
+ font-weight: 600;
117
+ }
118
+
119
+ .subheading3,
120
+ h5 {
121
+ font-size: calc(8px * 1.2917);
122
+ line-height: calc(1.2917 * var(--vspace));
123
+
124
+ font-weight: lighter;
125
+ text-transform: uppercase;
126
+ letter-spacing: 0.15em;
127
+ }
128
+
129
+ h6 {
130
+ font-size: calc(8px * 1.1667);
131
+ font-size: 1.1667em;
132
+ font-weight: normal;
133
+ font-style: italic;
134
+ font-family: "le-monde-livre-classic-byol", serif !important;
135
+ letter-spacing: 0px !important;
136
+ }
137
+
138
+ #start .md > *:first-child {
139
+ margin-top: 0;
140
+ }
141
+
142
+ h2 + h3 {
143
+ margin-top: 0;
144
+ }
145
+
146
+ .md hr {
147
+ border: none;
148
+ border-top: 1px solid var(--block-border-color);
149
+ margin: var(--vspace-2) 0 var(--vspace-2) 0;
150
+ }
151
+ .prose ul {
152
+ margin: var(--vspace-2) 0 var(--vspace-1) 0;
153
+ }
154
+
155
+ .gap {
156
+ gap: 0;
157
+ }
src/demo/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_workflowbuilder
src/demo/space.py ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import gradio as gr
3
+ from app import demo as app
4
+ import os
5
+
6
+ _docs = {'WorkflowBuilder': {'description': 'Professional Workflow Builder component with support for 25+ node types\ninspired by n8n and Langflow for AI agent development and MCP integration.', 'members': {'__init__': {'value': {'type': 'typing.Optional[typing.Dict[str, typing.Any]][\n typing.Dict[str, typing.Any][str, typing.Any], None\n]', 'default': 'None', 'description': 'Default workflow data with nodes and edges'}, 'label': {'type': 'typing.Optional[str][str, None]', 'default': 'None', 'description': 'Component label'}, 'info': {'type': 'typing.Optional[str][str, None]', 'default': 'None', 'description': 'Additional component information'}, 'show_label': {'type': 'typing.Optional[bool][bool, None]', 'default': 'None', 'description': 'Whether to show the label'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'Whether to use container styling'}, 'scale': {'type': 'typing.Optional[int][int, None]', 'default': 'None', 'description': 'Relative width scale'}, 'min_width': {'type': 'int', 'default': '160', 'description': 'Minimum width in pixels'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'Whether component is visible'}, 'elem_id': {'type': 'typing.Optional[str][str, None]', 'default': 'None', 'description': 'HTML element ID'}, 'elem_classes': {'type': 'typing.Optional[typing.List[str]][\n typing.List[str][str], None\n]', 'default': 'None', 'description': 'CSS classes'}, 'render': {'type': 'bool', 'default': 'True', 'description': 'Whether to render immediately'}}, 'postprocess': {'value': {'type': 'typing.Dict[str, typing.Any][str, typing.Any]', 'description': None}}, 'preprocess': {'return': {'type': 'typing.Dict[str, typing.Any][str, typing.Any]', 'description': None}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': 'Triggered when the value of the WorkflowBuilder changes either because of user input (e.g. a user types in a textbox) OR because of a function update (e.g. an image receives a value from the output of an event trigger). See `.input()` for a listener that is only triggered by user input.'}, 'input': {'type': None, 'default': None, 'description': 'This listener is triggered when the user changes the value of the WorkflowBuilder.'}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'WorkflowBuilder': []}}}
7
+
8
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
9
+
10
+ with gr.Blocks(
11
+ css=abs_path,
12
+ theme=gr.themes.Default(
13
+ font_mono=[
14
+ gr.themes.GoogleFont("Inconsolata"),
15
+ "monospace",
16
+ ],
17
+ ),
18
+ ) as demo:
19
+ gr.Markdown(
20
+ """
21
+ # `gradio_workflowbuilder`
22
+
23
+ <div style="display: flex; gap: 7px;">
24
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
25
+ </div>
26
+
27
+ workflow builder
28
+ """, elem_classes=["md-custom"], header_links=True)
29
+ app.render()
30
+ gr.Markdown(
31
+ """
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install gradio_workflowbuilder
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+ import gradio as gr
42
+ from gradio_workflowbuilder import WorkflowBuilder
43
+ import json
44
+
45
+
46
+ def export_workflow(workflow_data):
47
+ \"\"\"Export workflow as formatted JSON\"\"\"
48
+ if not workflow_data:
49
+ return "No workflow to export"
50
+ return json.dumps(workflow_data, indent=2)
51
+
52
+
53
+ # Create the main interface
54
+ with gr.Blocks(
55
+ title="🎨 Visual Workflow Builder",
56
+ theme=gr.themes.Soft(),
57
+ css=\"\"\"
58
+ .main-container { max-width: 1600px; margin: 0 auto; }
59
+ .workflow-section { margin-bottom: 2rem; }
60
+ .button-row { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
61
+
62
+ .component-description {
63
+ padding: 24px;
64
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
65
+ border-radius: 12px;
66
+ border-left: 4px solid #3b82f6;
67
+ margin: 16px 0;
68
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
69
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
70
+ }
71
+
72
+ .component-description p {
73
+ margin: 10px 0;
74
+ line-height: 1.6;
75
+ color: #374151;
76
+ }
77
+
78
+ .base-description {
79
+ font-size: 17px;
80
+ font-weight: 600;
81
+ color: #1e293b;
82
+ }
83
+
84
+ .base-description strong {
85
+ color: #3b82f6;
86
+ font-weight: 700;
87
+ }
88
+
89
+ .feature-description {
90
+ font-size: 16px;
91
+ color: #1e293b;
92
+ font-weight: 500;
93
+ }
94
+
95
+ .customization-note {
96
+ font-size: 15px;
97
+ color: #64748b;
98
+ font-style: italic;
99
+ }
100
+
101
+ .sample-intro {
102
+ font-size: 15px;
103
+ color: #1e293b;
104
+ font-weight: 600;
105
+ margin-top: 16px;
106
+ border-top: 1px solid #e2e8f0;
107
+ padding-top: 16px;
108
+ }
109
+ \"\"\"
110
+ ) as demo:
111
+
112
+ with gr.Column(elem_classes=["main-container"]):
113
+ gr.Markdown(\"\"\"
114
+ # 🎨 Visual Workflow Builder
115
+
116
+ **Create sophisticated workflows with drag and drop simplicity.**
117
+ \"\"\")
118
+
119
+ # Simple description section with styling
120
+ gr.HTML(\"\"\"
121
+ <div class="component-description">
122
+ <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
123
+ <p class="feature-description">Create custom workflows.</p>
124
+ <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
125
+ <p class="sample-intro">Here is a sample:</p>
126
+ </div>
127
+ \"\"\")
128
+
129
+ # Main workflow builder section
130
+ with gr.Column(elem_classes=["workflow-section"]):
131
+ workflow_builder = WorkflowBuilder(
132
+ label="🎨 Visual Workflow Designer",
133
+ info="Drag components from the sidebar → Connect nodes by dragging from output (right) to input (left) → Click nodes to edit properties"
134
+ )
135
+
136
+ # Export section below the workflow
137
+ gr.Markdown("## 💾 Export Workflow")
138
+
139
+ with gr.Row():
140
+ with gr.Column():
141
+ export_output = gr.Code(
142
+ language="json",
143
+ label="Workflow Configuration",
144
+ lines=10
145
+ )
146
+
147
+ # Action button
148
+ with gr.Row(elem_classes=["button-row"]):
149
+ export_btn = gr.Button("💾 Export JSON", variant="primary", size="lg")
150
+
151
+ # Instructions
152
+ with gr.Accordion("📖 How to Use", open=False):
153
+ gr.Markdown(\"\"\"
154
+ ### 🚀 Getting Started
155
+
156
+ 1. **Add Components**: Drag items from the left sidebar onto the canvas
157
+ 2. **Connect Nodes**: Drag from the blue circle on the right of a node to the left circle of another
158
+ 3. **Edit Properties**: Click any node to see its editable properties on the right panel
159
+ 4. **Organize**: Drag nodes around to create a clean workflow layout
160
+ 5. **Delete**: Use the ✕ button on nodes or click the red circle on connections
161
+
162
+ ### 🎯 Component Types
163
+
164
+ - **📥 Inputs**: Text fields, file uploads, number inputs
165
+ - **⚙️ Processing**: Language models, text processors, conditionals
166
+ - **📤 Outputs**: Text displays, file exports, charts
167
+ - **🔧 Tools**: API calls, data transformers, validators
168
+
169
+ ### 💡 Pro Tips
170
+
171
+ - **Collapsible Panels**: Use the arrow buttons to hide/show sidebars
172
+ - **Live Updates**: Properties update in real-time as you edit
173
+ - **Export Options**: Get JSON config for your workflow
174
+ \"\"\")
175
+
176
+ # Event handlers
177
+ export_btn.click(
178
+ fn=export_workflow,
179
+ inputs=[workflow_builder],
180
+ outputs=[export_output]
181
+ )
182
+
183
+
184
+ if __name__ == "__main__":
185
+ demo.launch(
186
+ server_name="0.0.0.0",
187
+ show_error=True
188
+ )
189
+
190
+ ```
191
+ """, elem_classes=["md-custom"], header_links=True)
192
+
193
+
194
+ gr.Markdown("""
195
+ ## `WorkflowBuilder`
196
+
197
+ ### Initialization
198
+ """, elem_classes=["md-custom"], header_links=True)
199
+
200
+ gr.ParamViewer(value=_docs["WorkflowBuilder"]["members"]["__init__"], linkify=[])
201
+
202
+
203
+ gr.Markdown("### Events")
204
+ gr.ParamViewer(value=_docs["WorkflowBuilder"]["events"], linkify=['Event'])
205
+
206
+
207
+
208
+
209
+ gr.Markdown("""
210
+
211
+ ### User function
212
+
213
+ The impact on the users predict function varies depending on whether the component is used as an input or output for an event (or both).
214
+
215
+ - When used as an Input, the component only impacts the input signature of the user function.
216
+ - When used as an output, the component only impacts the return signature of the user function.
217
+
218
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
219
+
220
+
221
+
222
+ ```python
223
+ def predict(
224
+ value: typing.Dict[str, typing.Any][str, typing.Any]
225
+ ) -> typing.Dict[str, typing.Any][str, typing.Any]:
226
+ return value
227
+ ```
228
+ """, elem_classes=["md-custom", "WorkflowBuilder-user-fn"], header_links=True)
229
+
230
+
231
+
232
+
233
+ demo.load(None, js=r"""function() {
234
+ const refs = {};
235
+ const user_fn_refs = {
236
+ WorkflowBuilder: [], };
237
+ requestAnimationFrame(() => {
238
+
239
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
240
+ if (refs.length > 0) {
241
+ const el = document.querySelector(`.${key}-user-fn`);
242
+ if (!el) return;
243
+ refs.forEach(ref => {
244
+ el.innerHTML = el.innerHTML.replace(
245
+ new RegExp("\\b"+ref+"\\b", "g"),
246
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
247
+ );
248
+ })
249
+ }
250
+ })
251
+
252
+ Object.entries(refs).forEach(([key, refs]) => {
253
+ if (refs.length > 0) {
254
+ const el = document.querySelector(`.${key}`);
255
+ if (!el) return;
256
+ refs.forEach(ref => {
257
+ el.innerHTML = el.innerHTML.replace(
258
+ new RegExp("\\b"+ref+"\\b", "g"),
259
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
260
+ );
261
+ })
262
+ }
263
+ })
264
+ })
265
+ }
266
+
267
+ """)
268
+
269
+ demo.launch()
src/frontend/Example.svelte ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+
4
+ export let value: string | null;
5
+ export let type: "gallery" | "table";
6
+ export let selected = false;
7
+
8
+ let size: number;
9
+ let el: HTMLDivElement;
10
+
11
+ function set_styles(element: HTMLElement, el_width: number): void {
12
+ element.style.setProperty(
13
+ "--local-text-width",
14
+ `${el_width && el_width < 150 ? el_width : 200}px`
15
+ );
16
+ element.style.whiteSpace = "unset";
17
+ }
18
+
19
+ onMount(() => {
20
+ set_styles(el, size);
21
+ });
22
+ </script>
23
+
24
+ <div
25
+ bind:clientWidth={size}
26
+ bind:this={el}
27
+ class:table={type === "table"}
28
+ class:gallery={type === "gallery"}
29
+ class:selected
30
+ >
31
+ {value ? value : ""}
32
+ </div>
33
+
34
+ <style>
35
+ .gallery {
36
+ padding: var(--size-1) var(--size-2);
37
+ }
38
+
39
+ div {
40
+ overflow: hidden;
41
+ min-width: var(--local-text-width);
42
+
43
+ white-space: nowrap;
44
+ }
45
+ </style>
src/frontend/Index.svelte ADDED
@@ -0,0 +1,2721 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { createEventDispatcher, onMount } from 'svelte';
3
+
4
+ export let value: { nodes: any[]; edges: any[] } = { nodes: [], edges: [] };
5
+ export let elem_id = "";
6
+ export let elem_classes: string[] = [];
7
+ export let visible = true;
8
+ export const container = true;
9
+ export const scale: number | null = null;
10
+ export let min_width: number | undefined = undefined;
11
+ export const gradio: any = {};
12
+
13
+ const dispatch = createEventDispatcher<{
14
+ change: { nodes: any[]; edges: any[] };
15
+ input: { nodes: any[]; edges: any[] };
16
+ }>();
17
+
18
+ // State management
19
+ let canvas: HTMLDivElement;
20
+ let canvasContainer: HTMLDivElement;
21
+ let isDragging = false;
22
+ let isDraggingFromSidebar = false;
23
+ let dragNode: any = null;
24
+ let dragOffset = { x: 0, y: 0 };
25
+ let isConnecting = false;
26
+ let connectionStart: any = null;
27
+ let mousePos = { x: 0, y: 0 };
28
+ let selectedNode: any = null;
29
+ let sidebarCollapsed = false;
30
+ let propertyPanelCollapsed = false;
31
+
32
+ // Workflow metadata
33
+ let workflowName = "My Workflow";
34
+ let workflowId = "workflow-" + Date.now();
35
+
36
+ // Zoom and pan state
37
+ let zoomLevel = 0.6;
38
+ let panOffset = { x: 0, y: 0 };
39
+ let isPanning = false;
40
+ let lastPanPoint = { x: 0, y: 0 };
41
+
42
+ // Define default workflow
43
+ const defaultWorkflow = {
44
+ workflow_id: "simple-rag-v1",
45
+ workflow_name: "Simple RAG Workflow",
46
+ nodes: [
47
+ {
48
+ id: "Input-1",
49
+ type: "Input",
50
+ position: { x: 30, y: 100 },
51
+ data: {
52
+ label: "Input",
53
+ display_name: "User Question",
54
+ template: {
55
+ data_type: {
56
+ display_name: "Data Type",
57
+ type: "options",
58
+ options: ["string", "image", "video", "audio", "file"],
59
+ value: "string"
60
+ },
61
+ value: {
62
+ display_name: "Value or Path",
63
+ type: "string",
64
+ value: "How do I get started with Modal?"
65
+ },
66
+ data: {
67
+ display_name: "Output Data",
68
+ type: "object",
69
+ is_handle: true
70
+ }
71
+ },
72
+ resources: {
73
+ cpu: 0.1,
74
+ memory: "128Mi",
75
+ gpu: "none"
76
+ }
77
+ }
78
+ },
79
+ {
80
+ id: "KnowledgeBase-1",
81
+ type: "KnowledgeBase",
82
+ position: { x: 50, y: 500 },
83
+ data: {
84
+ label: "Knowledge Base",
85
+ display_name: "Create Product Docs KB",
86
+ template: {
87
+ kb_name: {
88
+ display_name: "Knowledge Base Name",
89
+ type: "string",
90
+ value: "product-docs-v1"
91
+ },
92
+ source_type: {
93
+ display_name: "Source Type",
94
+ type: "options",
95
+ options: ["Directory", "URL"],
96
+ value: "URL"
97
+ },
98
+ path_or_url: {
99
+ display_name: "Path or URL",
100
+ type: "string",
101
+ value: "https://modal.com/docs/guide"
102
+ },
103
+ knowledge_base: {
104
+ display_name: "Knowledge Base Out",
105
+ type: "object",
106
+ is_handle: true
107
+ }
108
+ },
109
+ resources: {
110
+ cpu: 0.2,
111
+ memory: "256Mi",
112
+ gpu: "none"
113
+ }
114
+ }
115
+ },
116
+ {
117
+ id: "RAGQuery-1",
118
+ type: "RAGQuery",
119
+ position: { x: 400, y: 300 },
120
+ data: {
121
+ label: "RAG Query",
122
+ display_name: "Retrieve & Augment Prompt",
123
+ template: {
124
+ query: {
125
+ display_name: "Original Query",
126
+ type: "string",
127
+ is_handle: true
128
+ },
129
+ knowledge_base: {
130
+ display_name: "Knowledge Base",
131
+ type: "object",
132
+ is_handle: true
133
+ },
134
+ rag_prompt: {
135
+ display_name: "Augmented Prompt Out",
136
+ type: "string",
137
+ is_handle: true
138
+ }
139
+ },
140
+ resources: {
141
+ cpu: 0.3,
142
+ memory: "512Mi",
143
+ gpu: "none"
144
+ }
145
+ }
146
+ },
147
+ {
148
+ id: "ChatModel-1",
149
+ type: "ChatModel",
150
+ position: { x: 800, y: 200 },
151
+ data: {
152
+ label: "Chat Model",
153
+ display_name: "AI Assistant",
154
+ template: {
155
+ provider: {
156
+ display_name: "Provider",
157
+ type: "options",
158
+ options: ["OpenAI", "Anthropic"],
159
+ value: "OpenAI"
160
+ },
161
+ model: {
162
+ display_name: "Model Name",
163
+ type: "string",
164
+ value: "gpt-4o-mini"
165
+ },
166
+ api_key: {
167
+ display_name: "API Key",
168
+ type: "SecretStr",
169
+ required: true,
170
+ env_var: "OPENAI_API_KEY"
171
+ },
172
+ system_prompt: {
173
+ display_name: "System Prompt (Optional)",
174
+ type: "string",
175
+ value: "You are a helpful assistant that answers questions based on the provided context."
176
+ },
177
+ prompt: {
178
+ display_name: "Prompt",
179
+ type: "string",
180
+ is_handle: true
181
+ },
182
+ response: {
183
+ display_name: "Response",
184
+ type: "string",
185
+ is_handle: true
186
+ }
187
+ },
188
+ resources: {
189
+ cpu: 0.5,
190
+ memory: "512Mi",
191
+ gpu: "none"
192
+ }
193
+ }
194
+ },
195
+ {
196
+ id: "Output-1",
197
+ type: "Output",
198
+ position: { x: 1000, y: 600 },
199
+ data: {
200
+ label: "Output",
201
+ display_name: "Final Result",
202
+ template: {
203
+ input_data: {
204
+ display_name: "Input Data",
205
+ type: "object",
206
+ is_handle: true
207
+ }
208
+ },
209
+ resources: {
210
+ cpu: 0.1,
211
+ memory: "128Mi",
212
+ gpu: "none"
213
+ }
214
+ }
215
+ }
216
+ ],
217
+ edges: [
218
+ {
219
+ id: "e1-3",
220
+ source: "Input-1",
221
+ source_handle: "data",
222
+ target: "RAGQuery-1",
223
+ target_handle: "query"
224
+ },
225
+ {
226
+ id: "e2-3",
227
+ source: "KnowledgeBase-1",
228
+ source_handle: "knowledge_base",
229
+ target: "RAGQuery-1",
230
+ target_handle: "knowledge_base"
231
+ },
232
+ {
233
+ id: "e3-4",
234
+ source: "RAGQuery-1",
235
+ source_handle: "rag_prompt",
236
+ target: "ChatModel-1",
237
+ target_handle: "prompt"
238
+ },
239
+ {
240
+ id: "e4-5",
241
+ source: "ChatModel-1",
242
+ source_handle: "response",
243
+ target: "Output-1",
244
+ target_handle: "input_data"
245
+ }
246
+ ]
247
+ };
248
+
249
+ // Initialize nodes and edges
250
+ let nodes = value?.nodes?.length > 0 ? [...value.nodes] : defaultWorkflow.nodes;
251
+ let edges = value?.edges?.length > 0 ? [...value.edges] : defaultWorkflow.edges;
252
+
253
+ // Initialize workflow metadata
254
+ if (value?.workflow_name) {
255
+ workflowName = value.workflow_name;
256
+ }
257
+ if (value?.workflow_id) {
258
+ workflowId = value.workflow_id;
259
+ }
260
+
261
+ // Update value if empty
262
+ $: if (!value || !value.nodes || value.nodes.length === 0) {
263
+ value = defaultWorkflow;
264
+ }
265
+
266
+ // Component categories with new node types
267
+ const componentCategories = {
268
+ 'Input/Output': {
269
+ icon: '📥',
270
+ components: {
271
+ ChatInput: {
272
+ label: 'Chat Input',
273
+ icon: '💬',
274
+ color: '#4CAF50',
275
+ defaultData: {
276
+ display_name: 'Chat Input',
277
+ template: {
278
+ input_value: {
279
+ display_name: 'User Message',
280
+ type: 'string',
281
+ value: '',
282
+ is_handle: true
283
+ }
284
+ },
285
+ resources: {
286
+ cpu: 0.1,
287
+ memory: '128Mi',
288
+ gpu: 'none'
289
+ }
290
+ }
291
+ },
292
+ ChatOutput: {
293
+ label: 'Chat Output',
294
+ icon: '💭',
295
+ color: '#F44336',
296
+ defaultData: {
297
+ display_name: 'Chat Output',
298
+ template: {
299
+ response: {
300
+ display_name: 'AI Response',
301
+ type: 'string',
302
+ is_handle: true
303
+ }
304
+ },
305
+ resources: {
306
+ cpu: 0.1,
307
+ memory: '128Mi',
308
+ gpu: 'none'
309
+ }
310
+ }
311
+ },
312
+ Input: {
313
+ label: 'Input',
314
+ icon: '📥',
315
+ color: '#2196F3',
316
+ defaultData: {
317
+ display_name: 'Source Data',
318
+ template: {
319
+ data_type: {
320
+ display_name: 'Data Type',
321
+ type: 'options',
322
+ options: ['string', 'image', 'video', 'audio', 'file'],
323
+ value: 'string'
324
+ },
325
+ value: {
326
+ display_name: 'Value or Path',
327
+ type: 'string',
328
+ value: 'This is the initial text.'
329
+ },
330
+ data: {
331
+ display_name: 'Output Data',
332
+ type: 'object',
333
+ is_handle: true
334
+ }
335
+ },
336
+ resources: {
337
+ cpu: 0.1,
338
+ memory: '128Mi',
339
+ gpu: 'none'
340
+ }
341
+ }
342
+ },
343
+ Output: {
344
+ label: 'Output',
345
+ icon: '📤',
346
+ color: '#FF9800',
347
+ defaultData: {
348
+ display_name: 'Final Result',
349
+ template: {
350
+ input_data: {
351
+ display_name: 'Input Data',
352
+ type: 'object',
353
+ is_handle: true
354
+ }
355
+ },
356
+ resources: {
357
+ cpu: 0.1,
358
+ memory: '128Mi',
359
+ gpu: 'none'
360
+ }
361
+ }
362
+ }
363
+ }
364
+ },
365
+ 'AI & Language': {
366
+ icon: '🤖',
367
+ components: {
368
+ OpenAIModel: {
369
+ label: 'OpenAI Model',
370
+ icon: '🎯',
371
+ color: '#9C27B0',
372
+ defaultData: {
373
+ display_name: 'OpenAI Model',
374
+ template: {
375
+ model: {
376
+ display_name: 'Model',
377
+ type: 'options',
378
+ value: 'gpt-4',
379
+ options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo']
380
+ },
381
+ temperature: {
382
+ display_name: 'Temperature',
383
+ type: 'number',
384
+ value: 0.7,
385
+ min: 0,
386
+ max: 1
387
+ },
388
+ max_tokens: {
389
+ display_name: 'Max Tokens',
390
+ type: 'number',
391
+ value: 2048,
392
+ min: 1,
393
+ max: 4096
394
+ },
395
+ api_key: {
396
+ display_name: 'API Key',
397
+ type: 'SecretStr',
398
+ value: '',
399
+ env_var: 'OPENAI_API_KEY'
400
+ },
401
+ prompt: {
402
+ display_name: 'Prompt',
403
+ type: 'string',
404
+ is_handle: true
405
+ },
406
+ response: {
407
+ display_name: 'Response',
408
+ type: 'string',
409
+ is_handle: true
410
+ }
411
+ },
412
+ resources: {
413
+ cpu: 0.5,
414
+ memory: '512Mi',
415
+ gpu: 'none'
416
+ }
417
+ }
418
+ },
419
+ ChatModel: {
420
+ label: 'Chat Model',
421
+ icon: '💭',
422
+ color: '#673AB7',
423
+ defaultData: {
424
+ display_name: 'Chat Model',
425
+ template: {
426
+ provider: {
427
+ display_name: 'Provider',
428
+ type: 'options',
429
+ options: ['OpenAI', 'Anthropic'],
430
+ value: 'OpenAI'
431
+ },
432
+ model: {
433
+ display_name: 'Model',
434
+ type: 'string',
435
+ value: 'gpt-4o-mini'
436
+ },
437
+ api_key: {
438
+ display_name: 'API Key',
439
+ type: 'SecretStr',
440
+ required: true,
441
+ env_var: 'OPENAI_API_KEY'
442
+ },
443
+ system_prompt: {
444
+ display_name: 'System Prompt',
445
+ type: 'string',
446
+ value: 'You are a helpful assistant.'
447
+ },
448
+ prompt: {
449
+ display_name: 'Prompt',
450
+ type: 'string',
451
+ is_handle: true
452
+ },
453
+ response: {
454
+ display_name: 'Response',
455
+ type: 'string',
456
+ is_handle: true
457
+ }
458
+ },
459
+ resources: {
460
+ cpu: 0.5,
461
+ memory: '512Mi',
462
+ gpu: 'none'
463
+ }
464
+ }
465
+ },
466
+ Prompt: {
467
+ label: 'Prompt',
468
+ icon: '📝',
469
+ color: '#3F51B5',
470
+ defaultData: {
471
+ display_name: 'Prompt',
472
+ template: {
473
+ prompt_template: {
474
+ display_name: 'Template',
475
+ type: 'string',
476
+ value: '{{input}}',
477
+ is_handle: true
478
+ }
479
+ },
480
+ resources: {
481
+ cpu: 0.1,
482
+ memory: '128Mi',
483
+ gpu: 'none'
484
+ }
485
+ }
486
+ },
487
+ HFTextGeneration: {
488
+ label: 'HF Text Generation',
489
+ icon: '🤗',
490
+ color: '#E91E63',
491
+ defaultData: {
492
+ display_name: 'HF Text Generation',
493
+ template: {
494
+ model: {
495
+ display_name: 'Model',
496
+ type: 'string',
497
+ value: 'gpt2'
498
+ },
499
+ temperature: {
500
+ display_name: 'Temperature',
501
+ type: 'number',
502
+ value: 0.7,
503
+ min: 0,
504
+ max: 1
505
+ },
506
+ max_tokens: {
507
+ display_name: 'Max Tokens',
508
+ type: 'number',
509
+ value: 2048,
510
+ min: 1,
511
+ max: 4096
512
+ },
513
+ api_key: {
514
+ display_name: 'API Key',
515
+ type: 'SecretStr',
516
+ value: '',
517
+ env_var: 'HF_API_KEY'
518
+ },
519
+ prompt: {
520
+ display_name: 'Prompt',
521
+ type: 'string',
522
+ is_handle: true
523
+ },
524
+ response: {
525
+ display_name: 'Response',
526
+ type: 'string',
527
+ is_handle: true
528
+ }
529
+ },
530
+ resources: {
531
+ cpu: 0.3,
532
+ memory: '256Mi',
533
+ gpu: 'none'
534
+ }
535
+ }
536
+ },
537
+ ReActAgent: {
538
+ label: 'ReAct Agent',
539
+ icon: '🤖',
540
+ color: '#9C27B0',
541
+ defaultData: {
542
+ display_name: 'LlamaIndex ReAct Agent',
543
+ template: {
544
+ tools_input: {
545
+ display_name: 'Available Tools',
546
+ type: 'list',
547
+ is_handle: true,
548
+ info: 'Connect WebSearch, ExecutePython, APIRequest, and other tool nodes'
549
+ },
550
+ llm_model: {
551
+ display_name: 'LLM Model',
552
+ type: 'options',
553
+ options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k'],
554
+ value: 'gpt-4o-mini'
555
+ },
556
+ api_key: {
557
+ display_name: 'OpenAI API Key',
558
+ type: 'SecretStr',
559
+ required: true,
560
+ env_var: 'OPENAI_API_KEY'
561
+ },
562
+ system_prompt: {
563
+ display_name: 'System Prompt',
564
+ type: 'string',
565
+ value: 'You are a helpful AI assistant with access to various tools. Use the available tools to answer user questions accurately and efficiently.',
566
+ multiline: true
567
+ },
568
+ user_query: {
569
+ display_name: 'User Query',
570
+ type: 'string',
571
+ is_handle: true
572
+ },
573
+ max_iterations: {
574
+ display_name: 'Max Iterations',
575
+ type: 'number',
576
+ value: 8
577
+ },
578
+ temperature: {
579
+ display_name: 'Temperature',
580
+ type: 'number',
581
+ value: 0.1,
582
+ min: 0,
583
+ max: 2,
584
+ step: 0.1
585
+ },
586
+ verbose: {
587
+ display_name: 'Verbose Output',
588
+ type: 'boolean',
589
+ value: true
590
+ },
591
+ agent_response: {
592
+ display_name: 'Agent Response',
593
+ type: 'string',
594
+ is_handle: true
595
+ }
596
+ },
597
+ resources: {
598
+ cpu: 0.5,
599
+ memory: '512Mi',
600
+ gpu: 'none'
601
+ }
602
+ }
603
+ }
604
+ }
605
+ },
606
+ 'API & Web': {
607
+ icon: '🌐',
608
+ components: {
609
+ APIRequest: {
610
+ label: 'API Request',
611
+ icon: '🔌',
612
+ color: '#00BCD4',
613
+ defaultData: {
614
+ display_name: 'API Request',
615
+ template: {
616
+ url: {
617
+ display_name: 'URL',
618
+ type: 'string',
619
+ value: ''
620
+ },
621
+ method: {
622
+ display_name: 'Method',
623
+ type: 'options',
624
+ value: 'GET',
625
+ options: ['GET', 'POST', 'PUT', 'DELETE']
626
+ },
627
+ headers: {
628
+ display_name: 'Headers',
629
+ type: 'dict',
630
+ value: {}
631
+ },
632
+ body: {
633
+ display_name: 'Body',
634
+ type: 'string',
635
+ value: ''
636
+ },
637
+ response: {
638
+ display_name: 'Response',
639
+ type: 'object',
640
+ is_handle: true
641
+ }
642
+ },
643
+ resources: {
644
+ cpu: 0.2,
645
+ memory: '256Mi',
646
+ gpu: 'none'
647
+ }
648
+ }
649
+ },
650
+ WebSearch: {
651
+ label: 'Web Search',
652
+ icon: '🔍',
653
+ color: '#009688',
654
+ defaultData: {
655
+ display_name: 'Web Search',
656
+ template: {
657
+ query: {
658
+ display_name: 'Query',
659
+ type: 'string',
660
+ value: '',
661
+ is_handle: true
662
+ },
663
+ num_results: {
664
+ display_name: 'Number of Results',
665
+ type: 'number',
666
+ value: 5,
667
+ min: 1,
668
+ max: 10
669
+ },
670
+ api_key: {
671
+ display_name: 'API Key',
672
+ type: 'SecretStr',
673
+ value: '',
674
+ env_var: 'SERPAPI_KEY'
675
+ },
676
+ results: {
677
+ display_name: 'Search Results',
678
+ type: 'list',
679
+ is_handle: true
680
+ }
681
+ },
682
+ resources: {
683
+ cpu: 0.2,
684
+ memory: '256Mi',
685
+ gpu: 'none'
686
+ }
687
+ }
688
+ }
689
+ }
690
+ },
691
+ 'Data Processing': {
692
+ icon: '⚙️',
693
+ components: {
694
+ ExecutePython: {
695
+ label: 'Execute Python',
696
+ icon: '🐍',
697
+ color: '#FF5722',
698
+ defaultData: {
699
+ display_name: 'Execute Python',
700
+ template: {
701
+ code: {
702
+ display_name: 'Python Code',
703
+ type: 'string',
704
+ value: 'def process(input_data):\n return input_data'
705
+ },
706
+ timeout: {
707
+ display_name: 'Timeout',
708
+ type: 'number',
709
+ value: 30,
710
+ min: 1,
711
+ max: 300
712
+ },
713
+ input_data: {
714
+ display_name: 'Input Data',
715
+ type: 'object',
716
+ is_handle: true
717
+ },
718
+ output_data: {
719
+ display_name: 'Output Data',
720
+ type: 'object',
721
+ is_handle: true
722
+ }
723
+ },
724
+ resources: {
725
+ cpu: 0.3,
726
+ memory: '256Mi',
727
+ gpu: 'none'
728
+ }
729
+ }
730
+ },
731
+ ConditionalLogic: {
732
+ label: 'Conditional Logic',
733
+ icon: '🔀',
734
+ color: '#795548',
735
+ defaultData: {
736
+ display_name: 'Conditional Logic',
737
+ template: {
738
+ condition: {
739
+ display_name: 'Condition',
740
+ type: 'string',
741
+ value: '{{input}} == True'
742
+ },
743
+ input: {
744
+ display_name: 'Input',
745
+ type: 'object',
746
+ is_handle: true
747
+ },
748
+ true_output: {
749
+ display_name: 'True Output',
750
+ type: 'object',
751
+ is_handle: true
752
+ },
753
+ false_output: {
754
+ display_name: 'False Output',
755
+ type: 'object',
756
+ is_handle: true
757
+ }
758
+ },
759
+ resources: {
760
+ cpu: 0.1,
761
+ memory: '128Mi',
762
+ gpu: 'none'
763
+ }
764
+ }
765
+ },
766
+ Wait: {
767
+ label: 'Wait',
768
+ icon: '⏳',
769
+ color: '#607D8B',
770
+ defaultData: {
771
+ display_name: 'Wait',
772
+ template: {
773
+ seconds: {
774
+ display_name: 'Seconds',
775
+ type: 'number',
776
+ value: 1,
777
+ min: 1,
778
+ max: 3600
779
+ },
780
+ input: {
781
+ display_name: 'Input',
782
+ type: 'object',
783
+ is_handle: true
784
+ },
785
+ output: {
786
+ display_name: 'Output',
787
+ type: 'object',
788
+ is_handle: true
789
+ }
790
+ },
791
+ resources: {
792
+ cpu: 0.1,
793
+ memory: '128Mi',
794
+ gpu: 'none'
795
+ }
796
+ }
797
+ }
798
+ }
799
+ },
800
+ 'RAG & Knowledge': {
801
+ icon: '📚',
802
+ components: {
803
+ KnowledgeBase: {
804
+ label: 'Knowledge Base',
805
+ icon: '📖',
806
+ color: '#8BC34A',
807
+ defaultData: {
808
+ display_name: 'Knowledge Base',
809
+ template: {
810
+ kb_name: {
811
+ display_name: 'Knowledge Base Name',
812
+ type: 'string',
813
+ value: ''
814
+ },
815
+ source_type: {
816
+ display_name: 'Source Type',
817
+ type: 'options',
818
+ options: ['Directory', 'URL'],
819
+ value: 'Directory'
820
+ },
821
+ path_or_url: {
822
+ display_name: 'Path or URL',
823
+ type: 'string',
824
+ value: ''
825
+ },
826
+ knowledge_base: {
827
+ display_name: 'Knowledge Base',
828
+ type: 'object',
829
+ is_handle: true
830
+ }
831
+ },
832
+ resources: {
833
+ cpu: 0.2,
834
+ memory: '512Mi',
835
+ gpu: 'none'
836
+ }
837
+ }
838
+ },
839
+ RAGQuery: {
840
+ label: 'RAG Query',
841
+ icon: '🔎',
842
+ color: '#FFC107',
843
+ defaultData: {
844
+ display_name: 'RAG Query',
845
+ template: {
846
+ query: {
847
+ display_name: 'Query',
848
+ type: 'string',
849
+ is_handle: true
850
+ },
851
+ knowledge_base: {
852
+ display_name: 'Knowledge Base',
853
+ type: 'object',
854
+ is_handle: true
855
+ },
856
+ num_results: {
857
+ display_name: 'Number of Results',
858
+ type: 'number',
859
+ value: 3,
860
+ min: 1,
861
+ max: 10
862
+ },
863
+ rag_prompt: {
864
+ display_name: 'RAG Prompt',
865
+ type: 'string',
866
+ is_handle: true
867
+ }
868
+ },
869
+ resources: {
870
+ cpu: 0.3,
871
+ memory: '512Mi',
872
+ gpu: 'none'
873
+ }
874
+ }
875
+ }
876
+ }
877
+ },
878
+ 'Speech & Vision': {
879
+ icon: '👁️',
880
+ components: {
881
+ HFSpeechToText: {
882
+ label: 'HF Speech to Text',
883
+ icon: '🎤',
884
+ color: '#9E9E9E',
885
+ defaultData: {
886
+ display_name: 'HF Speech to Text',
887
+ template: {
888
+ model: {
889
+ display_name: 'Model',
890
+ type: 'string',
891
+ value: 'facebook/wav2vec2-base-960h'
892
+ },
893
+ api_key: {
894
+ display_name: 'API Key',
895
+ type: 'SecretStr',
896
+ value: '',
897
+ env_var: 'HF_API_KEY'
898
+ },
899
+ audio_input: {
900
+ display_name: 'Audio Input',
901
+ type: 'file',
902
+ is_handle: true
903
+ },
904
+ text_output: {
905
+ display_name: 'Text Output',
906
+ type: 'string',
907
+ is_handle: true
908
+ }
909
+ },
910
+ resources: {
911
+ cpu: 0.4,
912
+ memory: '512Mi',
913
+ gpu: 'optional'
914
+ }
915
+ }
916
+ },
917
+ HFTextToSpeech: {
918
+ label: 'HF Text to Speech',
919
+ icon: '🔊',
920
+ color: '#CDDC39',
921
+ defaultData: {
922
+ display_name: 'HF Text to Speech',
923
+ template: {
924
+ model: {
925
+ display_name: 'Model',
926
+ type: 'string',
927
+ value: 'facebook/fastspeech2-en-ljspeech'
928
+ },
929
+ api_key: {
930
+ display_name: 'API Key',
931
+ type: 'SecretStr',
932
+ value: '',
933
+ env_var: 'HF_API_KEY'
934
+ },
935
+ text_input: {
936
+ display_name: 'Text Input',
937
+ type: 'string',
938
+ is_handle: true
939
+ },
940
+ audio_output: {
941
+ display_name: 'Audio Output',
942
+ type: 'file',
943
+ is_handle: true
944
+ }
945
+ },
946
+ resources: {
947
+ cpu: 0.4,
948
+ memory: '512Mi',
949
+ gpu: 'optional'
950
+ }
951
+ }
952
+ },
953
+ HFSVisionModel: {
954
+ label: 'HF Vision Model',
955
+ icon: '👁️',
956
+ color: '#FF9800',
957
+ defaultData: {
958
+ display_name: 'HF Vision Model',
959
+ template: {
960
+ model: {
961
+ display_name: 'Model',
962
+ type: 'string',
963
+ value: 'google/vit-base-patch16-224'
964
+ },
965
+ api_key: {
966
+ display_name: 'API Key',
967
+ type: 'SecretStr',
968
+ value: '',
969
+ env_var: 'HF_API_KEY'
970
+ },
971
+ image_input: {
972
+ display_name: 'Image Input',
973
+ type: 'file',
974
+ is_handle: true
975
+ },
976
+ prediction: {
977
+ display_name: 'Prediction',
978
+ type: 'object',
979
+ is_handle: true
980
+ }
981
+ },
982
+ resources: {
983
+ cpu: 0.4,
984
+ memory: '512Mi',
985
+ gpu: 'required'
986
+ }
987
+ }
988
+ }
989
+ }
990
+ },
991
+ 'Image Generation': {
992
+ icon: '🎨',
993
+ components: {
994
+ HFImageGeneration: {
995
+ label: 'HF Image Generation',
996
+ icon: '🎨',
997
+ color: '#E91E63',
998
+ defaultData: {
999
+ display_name: 'HF Image Generation',
1000
+ template: {
1001
+ model: {
1002
+ display_name: 'Model',
1003
+ type: 'string',
1004
+ value: 'stabilityai/stable-diffusion-2'
1005
+ },
1006
+ prompt: {
1007
+ display_name: 'Prompt',
1008
+ type: 'string',
1009
+ value: '',
1010
+ is_handle: true
1011
+ },
1012
+ num_images: {
1013
+ display_name: 'Number of Images',
1014
+ type: 'number',
1015
+ value: 1,
1016
+ min: 1,
1017
+ max: 4
1018
+ },
1019
+ api_key: {
1020
+ display_name: 'API Key',
1021
+ type: 'SecretStr',
1022
+ value: '',
1023
+ env_var: 'HF_API_KEY'
1024
+ },
1025
+ images: {
1026
+ display_name: 'Generated Images',
1027
+ type: 'list',
1028
+ is_handle: true
1029
+ }
1030
+ },
1031
+ resources: {
1032
+ cpu: 0.5,
1033
+ memory: '1Gi',
1034
+ gpu: 'required'
1035
+ }
1036
+ }
1037
+ },
1038
+ NebiusImage: {
1039
+ label: 'Nebius Image',
1040
+ icon: '🖼️',
1041
+ color: '#2196F3',
1042
+ defaultData: {
1043
+ display_name: 'Nebius Image',
1044
+ template: {
1045
+ model: {
1046
+ display_name: 'Model',
1047
+ type: 'options',
1048
+ options: ['black-forest-labs/flux-dev', 'black-forest-labs/flux-schnell', 'stability-ai/sdxl'],
1049
+ value: 'black-forest-labs/flux-dev'
1050
+ },
1051
+ prompt: {
1052
+ display_name: 'Prompt',
1053
+ type: 'string',
1054
+ value: '',
1055
+ is_handle: true
1056
+ },
1057
+ negative_prompt: {
1058
+ display_name: 'Negative Prompt',
1059
+ type: 'string',
1060
+ value: ''
1061
+ },
1062
+ width: {
1063
+ display_name: 'Width',
1064
+ type: 'number',
1065
+ value: 1024
1066
+ },
1067
+ height: {
1068
+ display_name: 'Height',
1069
+ type: 'number',
1070
+ value: 1024
1071
+ },
1072
+ num_inference_steps: {
1073
+ display_name: 'Inference Steps',
1074
+ type: 'number',
1075
+ value: 28
1076
+ },
1077
+ seed: {
1078
+ display_name: 'Seed',
1079
+ type: 'number',
1080
+ value: -1
1081
+ },
1082
+ api_key: {
1083
+ display_name: 'API Key',
1084
+ type: 'SecretStr',
1085
+ value: '',
1086
+ env_var: 'NEBIUS_API_KEY'
1087
+ },
1088
+ image: {
1089
+ display_name: 'Generated Image',
1090
+ type: 'file',
1091
+ is_handle: true
1092
+ }
1093
+ },
1094
+ resources: {
1095
+ cpu: 0.5,
1096
+ memory: '1Gi',
1097
+ gpu: 'required'
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+ },
1103
+ 'MCP Integration': {
1104
+ icon: '🤝',
1105
+ components: {
1106
+ MCPConnection: {
1107
+ label: 'MCP Connection',
1108
+ icon: '🔌',
1109
+ color: '#673AB7',
1110
+ defaultData: {
1111
+ display_name: 'MCP Connection',
1112
+ template: {
1113
+ server_url: {
1114
+ display_name: 'Server URL',
1115
+ type: 'string',
1116
+ value: ''
1117
+ },
1118
+ connection_type: {
1119
+ display_name: 'Connection Type',
1120
+ type: 'options',
1121
+ options: ['http', 'stdio'],
1122
+ value: 'http'
1123
+ },
1124
+ allowed_tools: {
1125
+ display_name: 'Allowed Tools',
1126
+ type: 'string',
1127
+ value: ''
1128
+ },
1129
+ api_key: {
1130
+ display_name: 'API Key',
1131
+ type: 'SecretStr',
1132
+ value: '',
1133
+ env_var: 'MCP_API_KEY'
1134
+ },
1135
+ connection: {
1136
+ display_name: 'MCP Connection',
1137
+ type: 'object',
1138
+ is_handle: true
1139
+ }
1140
+ },
1141
+ resources: {
1142
+ cpu: 0.2,
1143
+ memory: '256Mi',
1144
+ gpu: 'none'
1145
+ }
1146
+ }
1147
+ },
1148
+ MCPAgent: {
1149
+ label: 'MCP Agent',
1150
+ icon: '🤖',
1151
+ color: '#3F51B5',
1152
+ defaultData: {
1153
+ display_name: 'MCP Agent',
1154
+ template: {
1155
+ llm_model: {
1156
+ display_name: 'LLM Model',
1157
+ type: 'options',
1158
+ options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k'],
1159
+ value: 'gpt-4o'
1160
+ },
1161
+ api_key: {
1162
+ display_name: 'OpenAI API Key',
1163
+ type: 'SecretStr',
1164
+ required: true,
1165
+ env_var: 'OPENAI_API_KEY'
1166
+ },
1167
+ system_prompt: {
1168
+ display_name: 'System Prompt',
1169
+ type: 'string',
1170
+ value: 'You are a helpful AI assistant.',
1171
+ multiline: true
1172
+ },
1173
+ max_iterations: {
1174
+ display_name: 'Max Iterations',
1175
+ type: 'number',
1176
+ value: 10,
1177
+ min: 1,
1178
+ max: 20
1179
+ },
1180
+ temperature: {
1181
+ display_name: 'Temperature',
1182
+ type: 'number',
1183
+ value: 0.1,
1184
+ min: 0,
1185
+ max: 2,
1186
+ step: 0.1
1187
+ },
1188
+ verbose: {
1189
+ display_name: 'Verbose Output',
1190
+ type: 'boolean',
1191
+ value: false
1192
+ },
1193
+ user_query: {
1194
+ display_name: 'User Query',
1195
+ type: 'string',
1196
+ is_handle: true
1197
+ },
1198
+ mcp_connection: {
1199
+ display_name: 'MCP Connection',
1200
+ type: 'object',
1201
+ is_handle: true
1202
+ },
1203
+ agent_response: {
1204
+ display_name: 'Agent Response',
1205
+ type: 'string',
1206
+ is_handle: true
1207
+ }
1208
+ },
1209
+ resources: {
1210
+ cpu: 0.5,
1211
+ memory: '512Mi',
1212
+ gpu: 'none'
1213
+ }
1214
+ }
1215
+ }
1216
+ }
1217
+ }
1218
+ };
1219
+
1220
+ // Property fields for each node type
1221
+ const propertyFields = {
1222
+ // Input/Output nodes
1223
+ ChatInput: [
1224
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1225
+ { key: 'template.input_value.display_name', label: 'Input Field Label', type: 'text', help: 'Label shown in the chat input field' },
1226
+ { key: 'template.input_value.value', label: 'Default Message', type: 'textarea', help: 'Default message shown in the input field' }
1227
+ ],
1228
+ ChatOutput: [
1229
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1230
+ { key: 'template.response.display_name', label: 'Response Field Label', type: 'text', help: 'Label shown in the chat output field' }
1231
+ ],
1232
+ Input: [
1233
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1234
+ { key: 'template.data_type.value', label: 'Data Type', type: 'select', options: ['string', 'image', 'video', 'audio', 'file'], help: 'Type of data this node will handle' },
1235
+ { key: 'template.value.value', label: 'Default Value', type: 'textarea', help: 'Default value or path' }
1236
+ ],
1237
+ Output: [
1238
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' }
1239
+ ],
1240
+
1241
+ // AI & Language nodes
1242
+ OpenAIModel: [
1243
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1244
+ { key: 'template.model.value', label: 'Model', type: 'select', options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo'] },
1245
+ { key: 'template.temperature.value', label: 'Temperature', type: 'number', min: 0, max: 1, step: 0.1 },
1246
+ { key: 'template.max_tokens.value', label: 'Max Tokens', type: 'number', min: 1, max: 4096 }
1247
+ ],
1248
+ ChatModel: [
1249
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1250
+ { key: 'template.provider.value', label: 'Provider', type: 'select', options: ['OpenAI', 'Anthropic'], help: 'AI model provider' },
1251
+ { key: 'template.model.value', label: 'Model', type: 'text', help: 'Model name' },
1252
+ { key: 'template.system_prompt.value', label: 'System Prompt', type: 'textarea', help: 'Optional system prompt' }
1253
+ ],
1254
+ Prompt: [
1255
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1256
+ { key: 'template.prompt_template.value', label: 'Prompt Template', type: 'textarea', help: 'Prompt template' }
1257
+ ],
1258
+ HFTextGeneration: [
1259
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1260
+ { key: 'template.model.value', label: 'Model', type: 'text', help: 'Model name' },
1261
+ { key: 'template.temperature.value', label: 'Temperature', type: 'number', min: 0, max: 1, step: 0.1, help: 'Model temperature' },
1262
+ { key: 'template.max_tokens.value', label: 'Max Tokens', type: 'number', min: 1, max: 4096, help: 'Maximum tokens' }
1263
+ ],
1264
+ ReActAgent: [
1265
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1266
+ { key: 'template.llm_model.value', label: 'LLM Model', type: 'select', options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k'], help: 'Model to use for the agent' },
1267
+ { key: 'template.system_prompt.value', label: 'System Prompt', type: 'textarea', help: 'System prompt for the agent', multiline: true },
1268
+ { key: 'template.max_iterations.value', label: 'Max Iterations', type: 'number', min: 1, max: 20, help: 'Maximum number of agent iterations' },
1269
+ { key: 'template.temperature.value', label: 'Temperature', type: 'number', min: 0, max: 2, step: 0.1, help: 'Model temperature (0-2)' },
1270
+ { key: 'template.verbose.value', label: 'Verbose Output', type: 'checkbox', help: 'Show detailed agent reasoning' }
1271
+ ],
1272
+
1273
+ // API & Web nodes
1274
+ APIRequest: [
1275
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1276
+ { key: 'template.url.value', label: 'URL', type: 'text', help: 'API endpoint URL' },
1277
+ { key: 'template.method.value', label: 'Method', type: 'select', options: ['GET', 'POST', 'PUT', 'DELETE'], help: 'HTTP method' }
1278
+ ],
1279
+ WebSearch: [
1280
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1281
+ { key: 'template.num_results.value', label: 'Number of Results', type: 'number', help: 'Number of search results' }
1282
+ ],
1283
+
1284
+ // Data Processing nodes
1285
+ ExecutePython: [
1286
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1287
+ { key: 'template.code.value', label: 'Python Code', type: 'textarea', help: 'Python code to execute' },
1288
+ { key: 'template.timeout.value', label: 'Timeout', type: 'number', help: 'Execution timeout' }
1289
+ ],
1290
+ ConditionalLogic: [
1291
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1292
+ { key: 'template.condition.value', label: 'Condition', type: 'text', help: 'Condition expression' }
1293
+ ],
1294
+ Wait: [
1295
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1296
+ { key: 'template.seconds.value', label: 'Seconds', type: 'number', help: 'Wait time in seconds' }
1297
+ ],
1298
+
1299
+ // RAG nodes
1300
+ KnowledgeBase: [
1301
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1302
+ { key: 'template.kb_name.value', label: 'Knowledge Base Name', type: 'text', help: 'Name for the knowledge base' },
1303
+ { key: 'template.source_type.value', label: 'Source Type', type: 'select', options: ['Directory', 'URL'], help: 'Type of source' },
1304
+ { key: 'template.path_or_url.value', label: 'Path or URL', type: 'text', help: 'Source location' }
1305
+ ],
1306
+ RAGQuery: [
1307
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1308
+ { key: 'template.num_results.value', label: 'Number of Results', type: 'number', help: 'Number of results to retrieve' }
1309
+ ],
1310
+
1311
+ // Speech & Vision nodes
1312
+ HFSpeechToText: [
1313
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1314
+ { key: 'template.model.value', label: 'Model', type: 'text', help: 'HuggingFace model ID' }
1315
+ ],
1316
+ HFTextToSpeech: [
1317
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1318
+ { key: 'template.model.value', label: 'Model', type: 'text', help: 'HuggingFace model ID' }
1319
+ ],
1320
+ HFSVisionModel: [
1321
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1322
+ { key: 'template.model.value', label: 'Model', type: 'text', help: 'HuggingFace model ID' }
1323
+ ],
1324
+
1325
+ // Image Generation nodes
1326
+ HFImageGeneration: [
1327
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1328
+ { key: 'template.model.value', label: 'Model', type: 'text', help: 'HuggingFace model ID' },
1329
+ { key: 'template.num_images.value', label: 'Number of Images', type: 'number', help: 'Number of images to generate' }
1330
+ ],
1331
+ NebiusImage: [
1332
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1333
+ { key: 'template.model.value', label: 'Model', type: 'select', options: ['black-forest-labs/flux-dev', 'black-forest-labs/flux-schnell', 'stability-ai/sdxl'], help: 'Nebius model to use' },
1334
+ { key: 'template.width.value', label: 'Width', type: 'number', help: 'Image width' },
1335
+ { key: 'template.height.value', label: 'Height', type: 'number', help: 'Image height' },
1336
+ { key: 'template.num_inference_steps.value', label: 'Inference Steps', type: 'number', help: 'Number of inference steps' },
1337
+ { key: 'template.seed.value', label: 'Seed', type: 'number', help: 'Random seed (-1 for random)' }
1338
+ ],
1339
+
1340
+ // MCP nodes
1341
+ MCPConnection: [
1342
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1343
+ { key: 'template.server_url.value', label: 'Server URL', type: 'text', help: 'MCP server URL' },
1344
+ { key: 'template.connection_type.value', label: 'Connection Type', type: 'select', options: ['http', 'stdio'], help: 'Connection type' },
1345
+ { key: 'template.allowed_tools.value', label: 'Allowed Tools', type: 'text', help: 'Optional list of allowed tools' }
1346
+ ],
1347
+ MCPAgent: [
1348
+ { key: 'display_name', label: 'Display Name', type: 'text', help: 'Name shown in the workflow' },
1349
+ { key: 'template.llm_model.value', label: 'LLM Model', type: 'select', options: ['gpt-4o', 'gpt-4o-mini', 'gpt-3.5-turbo', 'gpt-4', 'gpt-3.5-turbo-16k'], help: 'Model to use for the agent' },
1350
+ { key: 'template.system_prompt.value', label: 'System Prompt', type: 'textarea', help: 'System prompt for the agent', multiline: true },
1351
+ { key: 'template.max_iterations.value', label: 'Max Iterations', type: 'number', min: 1, max: 20, help: 'Maximum number of agent iterations' },
1352
+ { key: 'template.temperature.value', label: 'Temperature', type: 'number', min: 0, max: 2, step: 0.1, help: 'Model temperature (0-2)' },
1353
+ { key: 'template.verbose.value', label: 'Verbose Output', type: 'checkbox', help: 'Show detailed agent reasoning' }
1354
+ ]
1355
+ };
1356
+
1357
+ // Update parent component when data changes
1358
+ $: {
1359
+ const newValue = { nodes, edges };
1360
+ if (JSON.stringify(newValue) !== JSON.stringify(value)) {
1361
+ value = newValue;
1362
+ dispatch('change', newValue);
1363
+ }
1364
+ }
1365
+
1366
+ // Export workflow to JSON
1367
+
1368
+ // Clear workflow function
1369
+ function clearWorkflow() {
1370
+ nodes = [];
1371
+ edges = [];
1372
+ selectedNode = null;
1373
+ workflowName = "My Workflow";
1374
+ workflowId = "workflow-" + Date.now();
1375
+ }
1376
+
1377
+
1378
+ function exportWorkflow() {
1379
+ const exportData = {
1380
+ workflow_id: workflowId,
1381
+ workflow_name: workflowName,
1382
+ nodes: nodes.map(node => ({
1383
+ id: node.id,
1384
+ type: node.type,
1385
+ data: {
1386
+ display_name: node.data.display_name,
1387
+ template: node.data.template,
1388
+ resources: node.data.resources || {
1389
+ cpu: 0.1,
1390
+ memory: "128Mi",
1391
+ gpu: "none"
1392
+ }
1393
+ }
1394
+ })),
1395
+ edges: edges.map(edge => ({
1396
+ source: edge.source,
1397
+ source_handle: edge.source_handle || 'output',
1398
+ target: edge.target,
1399
+ target_handle: edge.target_handle || 'input'
1400
+ }))
1401
+ };
1402
+
1403
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
1404
+ const url = URL.createObjectURL(blob);
1405
+ const a = document.createElement('a');
1406
+ a.href = url;
1407
+ a.download = `${workflowName.replace(/\s+/g, '-').toLowerCase()}.json`;
1408
+ document.body.appendChild(a);
1409
+ a.click();
1410
+ document.body.removeChild(a);
1411
+ URL.revokeObjectURL(url);
1412
+ }
1413
+
1414
+ // Zoom functions
1415
+ function zoomIn() {
1416
+ zoomLevel = Math.min(zoomLevel * 1.2, 3);
1417
+ }
1418
+
1419
+ function zoomOut() {
1420
+ zoomLevel = Math.max(zoomLevel / 1.2, 0.3);
1421
+ }
1422
+
1423
+ function resetZoom() {
1424
+ zoomLevel = 1;
1425
+ panOffset = { x: 0, y: 0 };
1426
+ }
1427
+
1428
+ function handleWheel(event: WheelEvent) {
1429
+ event.preventDefault();
1430
+ if (event.ctrlKey || event.metaKey) {
1431
+ const delta = event.deltaY > 0 ? 0.9 : 1.1;
1432
+ zoomLevel = Math.max(0.3, Math.min(3, zoomLevel * delta));
1433
+ } else {
1434
+ panOffset.x -= event.deltaX * 0.5;
1435
+ panOffset.y -= event.deltaY * 0.5;
1436
+ panOffset = { ...panOffset };
1437
+ }
1438
+ }
1439
+
1440
+ // Pan functions
1441
+ function startPanning(event: MouseEvent) {
1442
+ if (event.button === 1 || (event.button === 0 && event.altKey)) {
1443
+ isPanning = true;
1444
+ lastPanPoint = { x: event.clientX, y: event.clientY };
1445
+ event.preventDefault();
1446
+ }
1447
+ }
1448
+
1449
+ function handlePanning(event: MouseEvent) {
1450
+ if (isPanning) {
1451
+ const deltaX = event.clientX - lastPanPoint.x;
1452
+ const deltaY = event.clientY - lastPanPoint.y;
1453
+ panOffset.x += deltaX;
1454
+ panOffset.y += deltaY;
1455
+ panOffset = { ...panOffset };
1456
+ lastPanPoint = { x: event.clientX, y: event.clientY };
1457
+ }
1458
+ }
1459
+
1460
+ function stopPanning() {
1461
+ isPanning = false;
1462
+ }
1463
+
1464
+ // Drag and drop from sidebar
1465
+ function handleSidebarDragStart(event: DragEvent, componentType: string, componentData: any) {
1466
+ if (event.dataTransfer) {
1467
+ event.dataTransfer.setData('application/json', JSON.stringify({
1468
+ type: componentType,
1469
+ data: componentData
1470
+ }));
1471
+ isDraggingFromSidebar = true;
1472
+ }
1473
+ }
1474
+
1475
+ function handleCanvasDropFromSidebar(event: DragEvent) {
1476
+ event.preventDefault();
1477
+ if (!isDraggingFromSidebar) return;
1478
+
1479
+ const rect = canvas.getBoundingClientRect();
1480
+ const x = (event.clientX - rect.left - panOffset.x) / zoomLevel;
1481
+ const y = (event.clientY - rect.top - panOffset.y) / zoomLevel;
1482
+
1483
+ try {
1484
+ const dropData = JSON.parse(event.dataTransfer?.getData('application/json') || '{}');
1485
+ if (dropData.type && dropData.data) {
1486
+ const newNode = {
1487
+ id: `${dropData.type}-${Date.now()}`,
1488
+ type: dropData.type,
1489
+ position: { x: Math.max(20, x - 160), y: Math.max(20, y - 80) },
1490
+ data: { ...dropData.data.defaultData, label: dropData.data.label }
1491
+ };
1492
+ nodes = [...nodes, newNode];
1493
+ }
1494
+ } catch (error) {
1495
+ console.error('Failed to parse drop data:', error);
1496
+ }
1497
+
1498
+ isDraggingFromSidebar = false;
1499
+ }
1500
+
1501
+ function handleCanvasDragOver(event: DragEvent) {
1502
+ event.preventDefault();
1503
+ }
1504
+
1505
+ // Node interaction handlers with proper event handling
1506
+ function handleMouseDown(event: MouseEvent, node: any) {
1507
+ // Only start dragging if clicking on the node header or empty areas
1508
+ if (event.target.closest('.node-property') ||
1509
+ event.target.closest('.property-input') ||
1510
+ event.target.closest('.property-select') ||
1511
+ event.target.closest('.property-checkbox')) {
1512
+ return; // Don't start dragging if clicking on form controls
1513
+ }
1514
+
1515
+ if (event.button !== 0) return;
1516
+
1517
+ isDragging = true;
1518
+ dragNode = node;
1519
+ const rect = canvas.getBoundingClientRect();
1520
+ const nodeScreenX = node.position.x * zoomLevel + panOffset.x;
1521
+ const nodeScreenY = node.position.y * zoomLevel + panOffset.y;
1522
+ dragOffset.x = event.clientX - rect.left - nodeScreenX;
1523
+ dragOffset.y = event.clientY - rect.top - nodeScreenY;
1524
+
1525
+ event.preventDefault();
1526
+ event.stopPropagation();
1527
+ }
1528
+
1529
+ function handleNodeClick(event: MouseEvent, node: any) {
1530
+ event.stopPropagation();
1531
+ selectedNode = { ...node };
1532
+ }
1533
+
1534
+ function handleMouseMove(event: MouseEvent) {
1535
+ const rect = canvas.getBoundingClientRect();
1536
+ mousePos.x = (event.clientX - rect.left - panOffset.x) / zoomLevel;
1537
+ mousePos.y = (event.clientY - rect.top - panOffset.y) / zoomLevel;
1538
+
1539
+ if (isDragging && dragNode) {
1540
+ const nodeIndex = nodes.findIndex(n => n.id === dragNode.id);
1541
+ if (nodeIndex >= 0) {
1542
+ const newX = Math.max(0, (event.clientX - rect.left - dragOffset.x - panOffset.x) / zoomLevel);
1543
+ const newY = Math.max(0, (event.clientY - rect.top - dragOffset.y - panOffset.y) / zoomLevel);
1544
+ nodes[nodeIndex].position.x = newX;
1545
+ nodes[nodeIndex].position.y = newY;
1546
+ nodes = [...nodes];
1547
+
1548
+ if (selectedNode?.id === dragNode.id) {
1549
+ selectedNode = { ...nodes[nodeIndex] };
1550
+ }
1551
+ }
1552
+ }
1553
+
1554
+ handlePanning(event);
1555
+ }
1556
+
1557
+ function handleMouseUp() {
1558
+ isDragging = false;
1559
+ dragNode = null;
1560
+ isConnecting = false;
1561
+ connectionStart = null;
1562
+ stopPanning();
1563
+ }
1564
+
1565
+ // Connection handling
1566
+ function startConnection(event: MouseEvent, nodeId: string) {
1567
+ event.stopPropagation();
1568
+ isConnecting = true;
1569
+ connectionStart = nodeId;
1570
+ }
1571
+
1572
+ function endConnection(event: MouseEvent, nodeId: string) {
1573
+ event.stopPropagation();
1574
+ if (isConnecting && connectionStart && connectionStart !== nodeId) {
1575
+ const existingEdge = edges.find(e =>
1576
+ (e.source === connectionStart && e.target === nodeId) ||
1577
+ (e.source === nodeId && e.target === connectionStart)
1578
+ );
1579
+
1580
+ if (!existingEdge) {
1581
+ const newEdge = {
1582
+ id: `e-${connectionStart}-${nodeId}-${Date.now()}`,
1583
+ source: connectionStart,
1584
+ target: nodeId
1585
+ };
1586
+ edges = [...edges, newEdge];
1587
+ }
1588
+ }
1589
+ isConnecting = false;
1590
+ connectionStart = null;
1591
+ }
1592
+
1593
+ // Node and edge management
1594
+ function deleteNode(nodeId: string) {
1595
+ nodes = nodes.filter(n => n.id !== nodeId);
1596
+ edges = edges.filter(e => e.source !== nodeId && e.target !== nodeId);
1597
+ if (selectedNode?.id === nodeId) {
1598
+ selectedNode = null;
1599
+ }
1600
+ }
1601
+
1602
+ function deleteEdge(edgeId: string) {
1603
+ edges = edges.filter(e => e.id !== edgeId);
1604
+ }
1605
+
1606
+ // Property updates with proper reactivity
1607
+ function updateNodeProperty(nodeId: string, key: string, value: any) {
1608
+ const nodeIndex = nodes.findIndex(n => n.id === nodeId);
1609
+ if (nodeIndex >= 0) {
1610
+ // Handle nested property paths
1611
+ const keyParts = key.split('.');
1612
+ let target = nodes[nodeIndex].data;
1613
+
1614
+ for (let i = 0; i < keyParts.length - 1; i++) {
1615
+ if (!target[keyParts[i]]) {
1616
+ target[keyParts[i]] = {};
1617
+ }
1618
+ target = target[keyParts[i]];
1619
+ }
1620
+
1621
+ target[keyParts[keyParts.length - 1]] = value;
1622
+ nodes = [...nodes]; // Trigger reactivity
1623
+
1624
+ if (selectedNode?.id === nodeId) {
1625
+ selectedNode = { ...nodes[nodeIndex] };
1626
+ }
1627
+ }
1628
+ }
1629
+
1630
+ function getNodeProperty(node: any, key: string) {
1631
+ const keyParts = key.split('.');
1632
+ let value = node.data;
1633
+
1634
+ for (const part of keyParts) {
1635
+ value = value?.[part];
1636
+ }
1637
+
1638
+ return value;
1639
+ }
1640
+
1641
+ // Panel toggle functions
1642
+ function toggleSidebar() {
1643
+ sidebarCollapsed = !sidebarCollapsed;
1644
+ }
1645
+
1646
+ function togglePropertyPanel() {
1647
+ propertyPanelCollapsed = !propertyPanelCollapsed;
1648
+ }
1649
+
1650
+ // Helper functions
1651
+ function getComponentConfig(type: string) {
1652
+ for (const category of Object.values(componentCategories)) {
1653
+ if (category.components[type]) {
1654
+ return category.components[type];
1655
+ }
1656
+ }
1657
+ return { label: type, icon: '⚡', color: '#6b7280' };
1658
+ }
1659
+
1660
+ function getConnectionPoints(sourceNode: any, targetNode: any) {
1661
+ const sourceX = sourceNode.position.x + 320;
1662
+ const sourceY = sourceNode.position.y + 80;
1663
+ const targetX = targetNode.position.x;
1664
+ const targetY = targetNode.position.y + 80;
1665
+
1666
+ return { sourceX, sourceY, targetX, targetY };
1667
+ }
1668
+
1669
+ // Canvas setup
1670
+ onMount(() => {
1671
+ document.addEventListener('mousemove', handleMouseMove);
1672
+ document.addEventListener('mouseup', handleMouseUp);
1673
+
1674
+ return () => {
1675
+ document.removeEventListener('mousemove', handleMouseMove);
1676
+ document.removeEventListener('mouseup', handleMouseUp);
1677
+ };
1678
+ });
1679
+ </script>
1680
+
1681
+ <div
1682
+ class="workflow-builder {elem_classes.join(' ')}"
1683
+ class:hide={!visible}
1684
+ style:min-width={min_width && min_width + "px"}
1685
+ id={elem_id}
1686
+ >
1687
+ <!-- Top Section: Main Workflow Area -->
1688
+ <div class="top-section">
1689
+ <!-- Left Sidebar -->
1690
+ <div class="sidebar" class:collapsed={sidebarCollapsed}>
1691
+ <div class="sidebar-header">
1692
+ {#if !sidebarCollapsed}
1693
+ <h3>Components</h3>
1694
+ {/if}
1695
+ <button
1696
+ class="toggle-btn sidebar-toggle"
1697
+ on:click={toggleSidebar}
1698
+ title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
1699
+ >
1700
+ {sidebarCollapsed ? '→' : '←'}
1701
+ </button>
1702
+ </div>
1703
+
1704
+ {#if !sidebarCollapsed}
1705
+ <div class="sidebar-content">
1706
+ {#each Object.entries(componentCategories) as [categoryName, category]}
1707
+ <div class="category">
1708
+ <div class="category-header">
1709
+ <span class="category-icon">{category.icon}</span>
1710
+ <span class="category-name">{categoryName}</span>
1711
+ </div>
1712
+
1713
+ <div class="category-components">
1714
+ {#each Object.entries(category.components) as [componentType, component]}
1715
+ <div
1716
+ class="component-item"
1717
+ draggable="true"
1718
+ on:dragstart={(e) => handleSidebarDragStart(e, componentType, component)}
1719
+ >
1720
+ <span class="component-icon">{component.icon}</span>
1721
+ <span class="component-label">{component.label}</span>
1722
+ </div>
1723
+ {/each}
1724
+ </div>
1725
+ </div>
1726
+ {/each}
1727
+ </div>
1728
+ {/if}
1729
+ </div>
1730
+
1731
+ <!-- Main Canvas Area -->
1732
+ <div class="canvas-area">
1733
+ <!-- Toolbar -->
1734
+ <div class="toolbar">
1735
+ <div class="toolbar-left">
1736
+ <input
1737
+ class="workflow-name-input"
1738
+ type="text"
1739
+ bind:value={workflowName}
1740
+ placeholder="Workflow Name"
1741
+ title="Enter workflow name"
1742
+ />
1743
+ </div>
1744
+ <div class="toolbar-center">
1745
+ <!-- Zoom Controls -->
1746
+ <div class="zoom-controls">
1747
+ <button class="zoom-btn" on:click={zoomOut} title="Zoom Out">-</button>
1748
+ <span class="zoom-level">{Math.round(zoomLevel * 100)}%</span>
1749
+ <button class="zoom-btn" on:click={zoomIn} title="Zoom In">+</button>
1750
+ <button class="zoom-btn reset" on:click={resetZoom} title="Reset View">⌂</button>
1751
+ </div>
1752
+ </div>
1753
+ <div class="toolbar-right">
1754
+ <span class="node-count">Nodes: {nodes.length}</span>
1755
+ <span class="edge-count">Edges: {edges.length}</span>
1756
+ <button class="clear-btn" on:click={clearWorkflow} title="Clear Workflow">
1757
+ 🗑️ Clear
1758
+ </button>
1759
+ </div>
1760
+ </div>
1761
+
1762
+ <!-- Canvas Container -->
1763
+ <div class="canvas-container" bind:this={canvasContainer}>
1764
+ <div
1765
+ class="canvas"
1766
+ bind:this={canvas}
1767
+ style="transform: scale({zoomLevel}) translate({panOffset.x / zoomLevel}px, {panOffset.y / zoomLevel}px);"
1768
+ on:drop={handleCanvasDropFromSidebar}
1769
+ on:dragover={handleCanvasDragOver}
1770
+ on:wheel={handleWheel}
1771
+ on:mousedown={startPanning}
1772
+ on:click={() => { selectedNode = null; }}
1773
+ >
1774
+ <!-- Grid Background -->
1775
+ <div class="grid-background"></div>
1776
+
1777
+ <!-- Edges (SVG) -->
1778
+ <svg class="edges-layer">
1779
+ {#each edges as edge (edge.id)}
1780
+ {@const sourceNode = nodes.find(n => n.id === edge.source)}
1781
+ {@const targetNode = nodes.find(n => n.id === edge.target)}
1782
+ {#if sourceNode && targetNode}
1783
+ {@const points = getConnectionPoints(sourceNode, targetNode)}
1784
+ <g class="edge-group">
1785
+ <path
1786
+ d="M {points.sourceX} {points.sourceY} C {points.sourceX + 80} {points.sourceY} {points.targetX - 80} {points.targetY} {points.targetX} {points.targetY}"
1787
+ stroke="#64748b"
1788
+ stroke-width="2"
1789
+ fill="none"
1790
+ class="edge-path"
1791
+ />
1792
+ <circle
1793
+ cx={points.targetX}
1794
+ cy={points.targetY}
1795
+ r="4"
1796
+ fill="#64748b"
1797
+ />
1798
+ <circle
1799
+ cx={(points.sourceX + points.targetX) / 2}
1800
+ cy={(points.sourceY + points.targetY) / 2}
1801
+ r="10"
1802
+ fill="#ef4444"
1803
+ class="edge-delete"
1804
+ on:click|stopPropagation={() => deleteEdge(edge.id)}
1805
+ />
1806
+ <text
1807
+ x={(points.sourceX + points.targetX) / 2}
1808
+ y={(points.sourceY + points.targetY) / 2 + 4}
1809
+ text-anchor="middle"
1810
+ class="edge-delete-text"
1811
+ on:click|stopPropagation={() => deleteEdge(edge.id)}
1812
+ >
1813
+
1814
+ </text>
1815
+ </g>
1816
+ {/if}
1817
+ {/each}
1818
+
1819
+ <!-- Connection preview -->
1820
+ {#if isConnecting && connectionStart}
1821
+ {@const startNode = nodes.find(n => n.id === connectionStart)}
1822
+ {#if startNode}
1823
+ <path
1824
+ d="M {startNode.position.x + 320} {startNode.position.y + 80} L {mousePos.x} {mousePos.y}"
1825
+ stroke="#3b82f6"
1826
+ stroke-width="3"
1827
+ stroke-dasharray="8,4"
1828
+ fill="none"
1829
+ opacity="0.8"
1830
+ />
1831
+ {/if}
1832
+ {/if}
1833
+ </svg>
1834
+
1835
+ <!-- FIXED: Nodes with guaranteed connection points -->
1836
+ {#each nodes as node (node.id)}
1837
+ {@const config = getComponentConfig(node.type)}
1838
+ <div
1839
+ class="node"
1840
+ class:selected={selectedNode?.id === node.id}
1841
+ style="left: {node.position.x}px; top: {node.position.y}px; border-color: {config.color};"
1842
+ on:mousedown={(e) => handleMouseDown(e, node)}
1843
+ on:click={(e) => handleNodeClick(e, node)}
1844
+ >
1845
+ <div class="node-header" style="background: {config.color};">
1846
+ <span class="node-icon">{config.icon}</span>
1847
+ <span class="node-title">{node.data.display_name || node.data.label}</span>
1848
+ <button
1849
+ class="node-delete"
1850
+ on:click|stopPropagation={() => deleteNode(node.id)}
1851
+ title="Delete node"
1852
+ >
1853
+
1854
+ </button>
1855
+ </div>
1856
+
1857
+ <div class="node-content">
1858
+ <!-- Dynamic property rendering based on node type -->
1859
+ {#if propertyFields[node.type]}
1860
+ {#each propertyFields[node.type].slice(0, 3) as field}
1861
+ <div class="node-property">
1862
+ <label class="property-label">{field.label}:</label>
1863
+ {#if field.type === 'select'}
1864
+ <select
1865
+ class="property-select"
1866
+ value={getNodeProperty(node, field.key) || ''}
1867
+ on:change={(e) => updateNodeProperty(node.id, field.key, e.target.value)}
1868
+ on:click|stopPropagation
1869
+ >
1870
+ {#each field.options as option}
1871
+ <option value={option}>{option}</option>
1872
+ {/each}
1873
+ </select>
1874
+ {:else if field.type === 'number'}
1875
+ <input
1876
+ class="property-input"
1877
+ type="number"
1878
+ min={field.min}
1879
+ max={field.max}
1880
+ step={field.step}
1881
+ value={getNodeProperty(node, field.key) || 0}
1882
+ on:input={(e) => updateNodeProperty(node.id, field.key, Number(e.target.value))}
1883
+ on:click|stopPropagation
1884
+ />
1885
+ {:else if field.type === 'checkbox'}
1886
+ <label class="property-checkbox">
1887
+ <input
1888
+ type="checkbox"
1889
+ checked={getNodeProperty(node, field.key) || false}
1890
+ on:change={(e) => updateNodeProperty(node.id, field.key, e.target.checked)}
1891
+ on:click|stopPropagation
1892
+ />
1893
+ <span>Yes</span>
1894
+ </label>
1895
+ {:else if field.type === 'textarea'}
1896
+ <textarea
1897
+ class="property-input"
1898
+ value={getNodeProperty(node, field.key) || ''}
1899
+ on:input={(e) => updateNodeProperty(node.id, field.key, e.target.value)}
1900
+ on:click|stopPropagation
1901
+ rows="2"
1902
+ ></textarea>
1903
+ {:else}
1904
+ <input
1905
+ class="property-input"
1906
+ type="text"
1907
+ value={getNodeProperty(node, field.key) || ''}
1908
+ on:input={(e) => updateNodeProperty(node.id, field.key, e.target.value)}
1909
+ on:click|stopPropagation
1910
+ />
1911
+ {/if}
1912
+ </div>
1913
+ {/each}
1914
+ {:else}
1915
+ <div class="node-status">Ready</div>
1916
+ {/if}
1917
+ </div>
1918
+
1919
+ <!-- FIXED: Connection points with fallback system -->
1920
+ {#if node.data.template}
1921
+ <!-- Try to create dynamic connection points based on template -->
1922
+ {@const templateHandles = Object.entries(node.data.template).filter(([_, handle]) => handle.is_handle)}
1923
+ {#each templateHandles as [handleId, handle], index}
1924
+ {#if handle.type === 'string' || handle.type === 'object' || handle.type === 'list' || handle.type === 'file'}
1925
+ <div
1926
+ class="connection-point {handle.type === 'string' || handle.type === 'list' || handle.type === 'file' ? 'output' : 'input'}"
1927
+ style="top: {index * 25 + 40}px; {(handle.type === 'string' || handle.type === 'list' || handle.type === 'file') ? 'right: -6px;' : 'left: -6px;'}"
1928
+ on:mouseup={(e) => (handle.type === 'object') && endConnection(e, node.id)}
1929
+ on:mousedown={(e) => (handle.type === 'string' || handle.type === 'list' || handle.type === 'file') && startConnection(e, node.id)}
1930
+ title={`${handle.display_name || handleId} (${handle.type})`}
1931
+ ></div>
1932
+ {/if}
1933
+ {/each}
1934
+
1935
+ <!-- FALLBACK: Ensure every node has at least basic connection points -->
1936
+ {@const hasInputHandles = templateHandles.some(([_, h]) => h.type === 'object')}
1937
+ {@const hasOutputHandles = templateHandles.some(([_, h]) => h.type === 'string' || h.type === 'list' || h.type === 'file')}
1938
+
1939
+ {#if !hasInputHandles}
1940
+ <div
1941
+ class="connection-point input"
1942
+ style="top: 50%; left: -6px; transform: translateY(-50%);"
1943
+ on:mouseup={(e) => endConnection(e, node.id)}
1944
+ title="Input"
1945
+ ></div>
1946
+ {/if}
1947
+
1948
+ {#if !hasOutputHandles}
1949
+ <div
1950
+ class="connection-point output"
1951
+ style="top: 50%; right: -6px; transform: translateY(-50%);"
1952
+ on:mousedown={(e) => startConnection(e, node.id)}
1953
+ title="Output"
1954
+ ></div>
1955
+ {/if}
1956
+ {:else}
1957
+ <!-- FALLBACK: Nodes without templates get basic connection points -->
1958
+ <div
1959
+ class="connection-point input"
1960
+ style="top: 50%; left: -6px; transform: translateY(-50%);"
1961
+ on:mouseup={(e) => endConnection(e, node.id)}
1962
+ title="Input"
1963
+ ></div>
1964
+ <div
1965
+ class="connection-point output"
1966
+ style="top: 50%; right: -6px; transform: translateY(-50%);"
1967
+ on:mousedown={(e) => startConnection(e, node.id)}
1968
+ title="Output"
1969
+ ></div>
1970
+ {/if}
1971
+ </div>
1972
+ {/each}
1973
+ </div>
1974
+ </div>
1975
+ </div>
1976
+
1977
+ <!-- Right Property Panel -->
1978
+ <div class="property-panel" class:collapsed={propertyPanelCollapsed}>
1979
+ <div class="property-header">
1980
+ {#if !propertyPanelCollapsed}
1981
+ <h3>Properties</h3>
1982
+ {/if}
1983
+ <button
1984
+ class="toggle-btn property-toggle"
1985
+ on:click={togglePropertyPanel}
1986
+ title={propertyPanelCollapsed ? 'Expand properties' : 'Collapse properties'}
1987
+ >
1988
+ {propertyPanelCollapsed ? '←' : '→'}
1989
+ </button>
1990
+ </div>
1991
+
1992
+ {#if !propertyPanelCollapsed}
1993
+ <div class="property-content">
1994
+ {#if selectedNode && propertyFields[selectedNode.type]}
1995
+ <div class="property-node-info">
1996
+ <h4>{selectedNode.data.display_name || selectedNode.data.label}</h4>
1997
+ <p class="property-node-type">TYPE: {selectedNode.type.toUpperCase()}</p>
1998
+ </div>
1999
+
2000
+ <div class="property-fields">
2001
+ {#each propertyFields[selectedNode.type] as field}
2002
+ <div class="property-field">
2003
+ <label for={field.key}>{field.label}</label>
2004
+ {#if field.help}
2005
+ <small class="field-help">{field.help}</small>
2006
+ {/if}
2007
+
2008
+ {#if field.type === 'text'}
2009
+ <input
2010
+ type="text"
2011
+ id={field.key}
2012
+ value={getNodeProperty(selectedNode, field.key) || ''}
2013
+ on:input={(e) => updateNodeProperty(selectedNode.id, field.key, e.target.value)}
2014
+ />
2015
+ {:else if field.type === 'number'}
2016
+ <input
2017
+ type="number"
2018
+ id={field.key}
2019
+ value={getNodeProperty(selectedNode, field.key) || 0}
2020
+ min={field.min}
2021
+ max={field.max}
2022
+ step={field.step}
2023
+ on:input={(e) => updateNodeProperty(selectedNode.id, field.key, Number(e.target.value))}
2024
+ />
2025
+ {:else if field.type === 'checkbox'}
2026
+ <label class="checkbox-label">
2027
+ <input
2028
+ type="checkbox"
2029
+ id={field.key}
2030
+ checked={getNodeProperty(selectedNode, field.key) || false}
2031
+ on:change={(e) => updateNodeProperty(selectedNode.id, field.key, e.target.checked)}
2032
+ />
2033
+ <span class="checkbox-text">Enable</span>
2034
+ </label>
2035
+ {:else if field.type === 'select'}
2036
+ <select
2037
+ id={field.key}
2038
+ value={getNodeProperty(selectedNode, field.key) || ''}
2039
+ on:change={(e) => updateNodeProperty(selectedNode.id, field.key, e.target.value)}
2040
+ >
2041
+ {#each field.options as option}
2042
+ <option value={option}>{option}</option>
2043
+ {/each}
2044
+ </select>
2045
+ {:else if field.type === 'textarea'}
2046
+ <textarea
2047
+ id={field.key}
2048
+ value={getNodeProperty(selectedNode, field.key) || ''}
2049
+ on:input={(e) => updateNodeProperty(selectedNode.id, field.key, e.target.value)}
2050
+ rows="4"
2051
+ ></textarea>
2052
+ {/if}
2053
+ </div>
2054
+ {/each}
2055
+ </div>
2056
+ {:else}
2057
+ <div class="property-empty">
2058
+ <div class="empty-icon">🎯</div>
2059
+ <p>Select a node to edit properties</p>
2060
+ <small>Click on any node to configure its detailed settings</small>
2061
+ </div>
2062
+ {/if}
2063
+ </div>
2064
+ {/if}
2065
+ </div>
2066
+ </div>
2067
+ </div>
2068
+
2069
+ <style>
2070
+ /* Base styles with proper sizing */
2071
+ .workflow-builder {
2072
+ width: 100%;
2073
+ height: 700px;
2074
+ border: 1px solid #e2e8f0;
2075
+ border-radius: 12px;
2076
+ display: flex;
2077
+ flex-direction: column;
2078
+ background: #ffffff;
2079
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
2080
+ overflow: hidden;
2081
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
2082
+ }
2083
+
2084
+ .hide {
2085
+ display: none;
2086
+ }
2087
+
2088
+ .top-section {
2089
+ flex: 1;
2090
+ display: flex;
2091
+ min-height: 0;
2092
+ }
2093
+
2094
+ /* Sidebar Styles */
2095
+ .sidebar {
2096
+ width: 240px;
2097
+ min-width: 240px;
2098
+ background: #f8fafc;
2099
+ border-right: 1px solid #e2e8f0;
2100
+ display: flex;
2101
+ flex-direction: column;
2102
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2103
+ position: relative;
2104
+ }
2105
+
2106
+ .sidebar.collapsed {
2107
+ width: 48px;
2108
+ min-width: 48px;
2109
+ }
2110
+
2111
+ .sidebar-header {
2112
+ padding: 12px;
2113
+ border-bottom: 1px solid #e2e8f0;
2114
+ display: flex;
2115
+ align-items: center;
2116
+ justify-content: space-between;
2117
+ background: white;
2118
+ min-height: 50px;
2119
+ box-sizing: border-box;
2120
+ }
2121
+
2122
+ .sidebar-header h3 {
2123
+ margin: 0;
2124
+ font-size: 15px;
2125
+ font-weight: 600;
2126
+ color: #1e293b;
2127
+ }
2128
+
2129
+ .toggle-btn {
2130
+ background: #f1f5f9;
2131
+ border: 1px solid #e2e8f0;
2132
+ border-radius: 6px;
2133
+ padding: 6px 8px;
2134
+ cursor: pointer;
2135
+ color: #64748b;
2136
+ font-size: 14px;
2137
+ transition: all 0.2s;
2138
+ min-width: 28px;
2139
+ height: 28px;
2140
+ display: flex;
2141
+ align-items: center;
2142
+ justify-content: center;
2143
+ z-index: 10;
2144
+ position: relative;
2145
+ }
2146
+
2147
+ .toggle-btn:hover {
2148
+ background: #e2e8f0;
2149
+ color: #475569;
2150
+ }
2151
+
2152
+ .sidebar-toggle {
2153
+ position: absolute;
2154
+ right: 8px;
2155
+ top: 50%;
2156
+ transform: translateY(-50%);
2157
+ }
2158
+
2159
+ .sidebar-content {
2160
+ flex: 1;
2161
+ overflow-y: auto;
2162
+ padding: 12px;
2163
+ }
2164
+
2165
+ .category {
2166
+ margin-bottom: 12px;
2167
+ }
2168
+
2169
+ .category-header {
2170
+ display: flex;
2171
+ align-items: center;
2172
+ padding: 6px 0;
2173
+ font-weight: 600;
2174
+ font-size: 12px;
2175
+ color: #374151;
2176
+ border-bottom: 1px solid #e5e7eb;
2177
+ margin-bottom: 6px;
2178
+ }
2179
+
2180
+ .category-icon {
2181
+ margin-right: 6px;
2182
+ font-size: 14px;
2183
+ }
2184
+
2185
+ .component-item {
2186
+ display: flex;
2187
+ align-items: center;
2188
+ padding: 6px 8px;
2189
+ margin-bottom: 3px;
2190
+ background: white;
2191
+ border: 1px solid #e5e7eb;
2192
+ border-radius: 6px;
2193
+ cursor: grab;
2194
+ transition: all 0.2s ease;
2195
+ font-size: 12px;
2196
+ }
2197
+
2198
+ .component-item:hover {
2199
+ background: #f8fafc;
2200
+ border-color: #cbd5e1;
2201
+ transform: translateX(2px);
2202
+ }
2203
+
2204
+ .component-item:active {
2205
+ cursor: grabbing;
2206
+ }
2207
+
2208
+ .component-icon {
2209
+ margin-right: 6px;
2210
+ font-size: 14px;
2211
+ }
2212
+
2213
+ .component-label {
2214
+ font-weight: 500;
2215
+ color: #374151;
2216
+ }
2217
+
2218
+ /* Canvas Area Styles */
2219
+ .canvas-area {
2220
+ flex: 1;
2221
+ display: flex;
2222
+ flex-direction: column;
2223
+ min-width: 400px;
2224
+ }
2225
+
2226
+ .toolbar {
2227
+ height: 50px;
2228
+ border-bottom: 1px solid #e2e8f0;
2229
+ display: flex;
2230
+ align-items: center;
2231
+ justify-content: space-between;
2232
+ padding: 0 16px;
2233
+ background: white;
2234
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
2235
+ }
2236
+
2237
+ .workflow-name-input {
2238
+ font-size: 16px;
2239
+ font-weight: 600;
2240
+ color: #1e293b;
2241
+ border: none;
2242
+ background: transparent;
2243
+ outline: none;
2244
+ padding: 4px 8px;
2245
+ border-radius: 4px;
2246
+ transition: background 0.2s;
2247
+ }
2248
+
2249
+ .workflow-name-input:hover,
2250
+ .workflow-name-input:focus {
2251
+ background: #f1f5f9;
2252
+ }
2253
+
2254
+ .toolbar-center {
2255
+ display: flex;
2256
+ align-items: center;
2257
+ }
2258
+
2259
+ .zoom-controls {
2260
+ display: flex;
2261
+ align-items: center;
2262
+ gap: 4px;
2263
+ background: #f1f5f9;
2264
+ padding: 4px;
2265
+ border-radius: 8px;
2266
+ border: 1px solid #e2e8f0;
2267
+ }
2268
+
2269
+ .zoom-btn {
2270
+ background: white;
2271
+ border: none;
2272
+ width: 28px;
2273
+ height: 28px;
2274
+ border-radius: 4px;
2275
+ cursor: pointer;
2276
+ font-weight: 600;
2277
+ display: flex;
2278
+ align-items: center;
2279
+ justify-content: center;
2280
+ transition: all 0.2s;
2281
+ font-size: 14px;
2282
+ }
2283
+
2284
+ .zoom-btn:hover {
2285
+ background: #e2e8f0;
2286
+ }
2287
+
2288
+ .zoom-btn.reset {
2289
+ font-size: 12px;
2290
+ }
2291
+
2292
+ .zoom-level {
2293
+ font-size: 12px;
2294
+ font-weight: 600;
2295
+ color: #64748b;
2296
+ min-width: 40px;
2297
+ text-align: center;
2298
+ }
2299
+
2300
+ .toolbar-right {
2301
+ display: flex;
2302
+ gap: 12px;
2303
+ font-size: 12px;
2304
+ align-items: center;
2305
+ }
2306
+
2307
+ .node-count, .edge-count {
2308
+ color: #64748b;
2309
+ background: #f1f5f9;
2310
+ padding: 4px 8px;
2311
+ border-radius: 12px;
2312
+ font-weight: 500;
2313
+ }
2314
+
2315
+ .export-btn {
2316
+ background: #3b82f6;
2317
+ color: white;
2318
+ border: none;
2319
+ padding: 6px 12px;
2320
+ border-radius: 6px;
2321
+ font-size: 12px;
2322
+ font-weight: 500;
2323
+ cursor: pointer;
2324
+ transition: all 0.2s;
2325
+ display: flex;
2326
+ align-items: center;
2327
+ gap: 4px;
2328
+ }
2329
+
2330
+ .export-btn:hover {
2331
+ background: #2563eb;
2332
+ transform: translateY(-1px);
2333
+ }
2334
+
2335
+ .canvas-container {
2336
+ flex: 1;
2337
+ position: relative;
2338
+ overflow: hidden;
2339
+ background: #fafbfc;
2340
+ cursor: grab;
2341
+ }
2342
+
2343
+ .canvas-container:active {
2344
+ cursor: grabbing;
2345
+ }
2346
+
2347
+ .canvas {
2348
+ position: absolute;
2349
+ top: 0;
2350
+ left: 0;
2351
+ width: 4000px;
2352
+ height: 4000px;
2353
+ transform-origin: 0 0;
2354
+ }
2355
+
2356
+ .grid-background {
2357
+ position: absolute;
2358
+ top: 0;
2359
+ left: 0;
2360
+ width: 100%;
2361
+ height: 100%;
2362
+ background-image:
2363
+ radial-gradient(circle, #e2e8f0 1px, transparent 1px);
2364
+ background-size: 20px 20px;
2365
+ pointer-events: none;
2366
+ opacity: 0.6;
2367
+ }
2368
+
2369
+ .edges-layer {
2370
+ position: absolute;
2371
+ top: 0;
2372
+ left: 0;
2373
+ width: 100%;
2374
+ height: 100%;
2375
+ pointer-events: none;
2376
+ z-index: 1;
2377
+ }
2378
+
2379
+ .edge-delete, .edge-delete-text {
2380
+ pointer-events: all;
2381
+ cursor: pointer;
2382
+ }
2383
+
2384
+ .edge-delete-text {
2385
+ font-size: 10px;
2386
+ fill: white;
2387
+ text-anchor: middle;
2388
+ user-select: none;
2389
+ }
2390
+
2391
+ .edge-delete:hover {
2392
+ fill: #dc2626;
2393
+ }
2394
+
2395
+ /* Node styles with proper sizing and no overflow */
2396
+ .node {
2397
+ position: absolute;
2398
+ width: 320px;
2399
+ min-height: 160px;
2400
+ background: white;
2401
+ border: 2px solid #e2e8f0;
2402
+ border-radius: 10px;
2403
+ cursor: move;
2404
+ user-select: none;
2405
+ z-index: 2;
2406
+ transition: all 0.2s ease;
2407
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2408
+ overflow: visible;
2409
+ }
2410
+
2411
+ .node:hover {
2412
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
2413
+ transform: translateY(-1px);
2414
+ }
2415
+
2416
+ .node.selected {
2417
+ border-color: #3b82f6;
2418
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1), 0 4px 16px rgba(0, 0, 0, 0.15);
2419
+ }
2420
+
2421
+ .node-header {
2422
+ display: flex;
2423
+ align-items: center;
2424
+ padding: 12px 16px;
2425
+ color: white;
2426
+ font-weight: 600;
2427
+ font-size: 14px;
2428
+ border-radius: 8px 8px 0 0;
2429
+ min-height: 24px;
2430
+ }
2431
+
2432
+ .node-icon {
2433
+ margin-right: 8px;
2434
+ font-size: 16px;
2435
+ flex-shrink: 0;
2436
+ }
2437
+
2438
+ .node-title {
2439
+ flex: 1;
2440
+ overflow: hidden;
2441
+ text-overflow: ellipsis;
2442
+ white-space: nowrap;
2443
+ }
2444
+
2445
+ .node-delete {
2446
+ background: rgba(255, 255, 255, 0.2);
2447
+ border: none;
2448
+ color: white;
2449
+ cursor: pointer;
2450
+ font-size: 12px;
2451
+ padding: 4px 6px;
2452
+ border-radius: 4px;
2453
+ transition: all 0.2s;
2454
+ flex-shrink: 0;
2455
+ }
2456
+
2457
+ .node-delete:hover {
2458
+ background: rgba(255, 255, 255, 0.3);
2459
+ }
2460
+
2461
+ .node-content {
2462
+ padding: 12px 16px;
2463
+ max-height: 200px;
2464
+ overflow-y: auto;
2465
+ overflow-x: hidden;
2466
+ }
2467
+
2468
+ .node-property {
2469
+ display: flex;
2470
+ flex-direction: column;
2471
+ gap: 4px;
2472
+ margin-bottom: 12px;
2473
+ font-size: 12px;
2474
+ }
2475
+
2476
+ .property-label {
2477
+ font-weight: 600;
2478
+ color: #374151;
2479
+ font-size: 11px;
2480
+ margin-bottom: 2px;
2481
+ }
2482
+
2483
+ .property-input, .property-select {
2484
+ width: 100%;
2485
+ padding: 6px 8px;
2486
+ border: 1px solid #d1d5db;
2487
+ border-radius: 4px;
2488
+ font-size: 11px;
2489
+ background: white;
2490
+ transition: all 0.2s;
2491
+ box-sizing: border-box;
2492
+ resize: vertical;
2493
+ }
2494
+
2495
+ .property-input:focus, .property-select:focus {
2496
+ outline: none;
2497
+ border-color: #3b82f6;
2498
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
2499
+ }
2500
+
2501
+ .property-input:hover, .property-select:hover {
2502
+ border-color: #9ca3af;
2503
+ }
2504
+
2505
+ .property-checkbox {
2506
+ display: flex;
2507
+ align-items: center;
2508
+ gap: 6px;
2509
+ font-size: 11px;
2510
+ color: #374151;
2511
+ cursor: pointer;
2512
+ }
2513
+
2514
+ .property-checkbox input[type="checkbox"] {
2515
+ width: auto;
2516
+ margin: 0;
2517
+ cursor: pointer;
2518
+ }
2519
+
2520
+ .node-status {
2521
+ font-size: 12px;
2522
+ color: #64748b;
2523
+ text-align: center;
2524
+ padding: 20px;
2525
+ font-style: italic;
2526
+ }
2527
+
2528
+ /* FIXED: Connection points that work for ALL nodes */
2529
+ .connection-point {
2530
+ position: absolute;
2531
+ width: 12px;
2532
+ height: 12px;
2533
+ border-radius: 50%;
2534
+ background: #3b82f6;
2535
+ border: 2px solid white;
2536
+ cursor: crosshair;
2537
+ z-index: 3;
2538
+ transition: all 0.2s ease;
2539
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
2540
+ }
2541
+
2542
+ .connection-point.input {
2543
+ left: -6px;
2544
+ }
2545
+
2546
+ .connection-point.output {
2547
+ right: -6px;
2548
+ }
2549
+
2550
+ .connection-point:hover {
2551
+ background: #2563eb;
2552
+ transform: scale(1.2);
2553
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
2554
+ }
2555
+
2556
+ /* Property Panel Styles */
2557
+ .property-panel {
2558
+ width: 280px;
2559
+ min-width: 280px;
2560
+ background: #f8fafc;
2561
+ border-left: 1px solid #e2e8f0;
2562
+ display: flex;
2563
+ flex-direction: column;
2564
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2565
+ position: relative;
2566
+ }
2567
+
2568
+ .property-panel.collapsed {
2569
+ width: 48px;
2570
+ min-width: 48px;
2571
+ }
2572
+
2573
+ .property-header {
2574
+ padding: 12px;
2575
+ border-bottom: 1px solid #e2e8f0;
2576
+ display: flex;
2577
+ align-items: center;
2578
+ justify-content: space-between;
2579
+ background: white;
2580
+ min-height: 50px;
2581
+ box-sizing: border-box;
2582
+ }
2583
+
2584
+ .property-header h3 {
2585
+ margin: 0;
2586
+ font-size: 15px;
2587
+ font-weight: 600;
2588
+ color: #1e293b;
2589
+ }
2590
+
2591
+ .property-toggle {
2592
+ position: absolute;
2593
+ left: 8px;
2594
+ top: 50%;
2595
+ transform: translateY(-50%);
2596
+ }
2597
+
2598
+ .property-content {
2599
+ flex: 1;
2600
+ overflow-y: auto;
2601
+ padding: 16px;
2602
+ }
2603
+
2604
+ .property-node-info {
2605
+ margin-bottom: 20px;
2606
+ padding: 12px;
2607
+ background: white;
2608
+ border-radius: 8px;
2609
+ border: 1px solid #e2e8f0;
2610
+ }
2611
+
2612
+ .property-node-info h4 {
2613
+ margin: 0 0 4px 0;
2614
+ font-size: 16px;
2615
+ color: #1e293b;
2616
+ }
2617
+
2618
+ .property-node-type {
2619
+ margin: 0;
2620
+ font-size: 11px;
2621
+ color: #64748b;
2622
+ text-transform: uppercase;
2623
+ font-weight: 600;
2624
+ }
2625
+
2626
+ .property-field {
2627
+ margin-bottom: 16px;
2628
+ }
2629
+
2630
+ .property-field label {
2631
+ display: block;
2632
+ margin-bottom: 6px;
2633
+ font-size: 13px;
2634
+ font-weight: 600;
2635
+ color: #374151;
2636
+ }
2637
+
2638
+ .field-help {
2639
+ display: block;
2640
+ margin-bottom: 4px;
2641
+ font-size: 11px;
2642
+ color: #64748b;
2643
+ font-style: italic;
2644
+ }
2645
+
2646
+ .property-field input,
2647
+ .property-field select,
2648
+ .property-field textarea {
2649
+ width: 100%;
2650
+ padding: 8px 10px;
2651
+ border: 1px solid #d1d5db;
2652
+ border-radius: 6px;
2653
+ font-size: 13px;
2654
+ background: white;
2655
+ transition: border-color 0.2s;
2656
+ box-sizing: border-box;
2657
+ }
2658
+
2659
+ .property-field input:focus,
2660
+ .property-field select:focus,
2661
+ .property-field textarea:focus {
2662
+ outline: none;
2663
+ border-color: #3b82f6;
2664
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
2665
+ }
2666
+
2667
+ .checkbox-label {
2668
+ display: flex !important;
2669
+ align-items: center;
2670
+ margin-bottom: 0 !important;
2671
+ cursor: pointer;
2672
+ }
2673
+
2674
+ .checkbox-label input[type="checkbox"] {
2675
+ width: auto !important;
2676
+ margin-right: 8px !important;
2677
+ }
2678
+
2679
+ .property-empty {
2680
+ text-align: center;
2681
+ padding: 40px 16px;
2682
+ color: #64748b;
2683
+ }
2684
+
2685
+ .empty-icon {
2686
+ font-size: 32px;
2687
+ margin-bottom: 12px;
2688
+ opacity: 0.5;
2689
+ }
2690
+
2691
+ .property-empty p {
2692
+ margin: 0 0 6px 0;
2693
+ font-size: 14px;
2694
+ font-weight: 500;
2695
+ }
2696
+
2697
+ .property-empty small {
2698
+ font-size: 12px;
2699
+ opacity: 0.7;
2700
+ }
2701
+ .clear-btn {
2702
+ background: #ef4444;
2703
+ color: white;
2704
+ border: none;
2705
+ padding: 6px 12px;
2706
+ border-radius: 6px;
2707
+ font-size: 12px;
2708
+ font-weight: 500;
2709
+ cursor: pointer;
2710
+ transition: all 0.2s;
2711
+ display: flex;
2712
+ align-items: center;
2713
+ gap: 4px;
2714
+ }
2715
+
2716
+ .clear-btn:hover {
2717
+ background: #dc2626;
2718
+ transform: translateY(-1px);
2719
+ }
2720
+
2721
+ </style>
src/frontend/gradio.config.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: [],
3
+ svelte: {
4
+ preprocess: [],
5
+ },
6
+ build: {
7
+ target: "modules",
8
+ },
9
+ };
src/frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
src/frontend/package.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "gradio_workflowbuilder",
3
+ "version": "0.3.22",
4
+ "description": "Gradio UI packages",
5
+ "type": "module",
6
+ "author": "",
7
+ "license": "ISC",
8
+ "private": false,
9
+ "main_changeset": true,
10
+ "exports": {
11
+ ".": {
12
+ "gradio": "./Index.svelte",
13
+ "svelte": "./dist/Index.svelte",
14
+ "types": "./dist/Index.svelte.d.ts"
15
+ },
16
+ "./example": {
17
+ "gradio": "./Example.svelte",
18
+ "svelte": "./dist/Example.svelte",
19
+ "types": "./dist/Example.svelte.d.ts"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "dependencies": {
24
+ "@gradio/atoms": "0.16.1",
25
+ "@gradio/icons": "0.12.0",
26
+ "@gradio/statustracker": "0.10.12",
27
+ "@gradio/utils": "0.10.2",
28
+ "@xyflow/svelte": "^1.0.2"
29
+ },
30
+ "devDependencies": {
31
+ "@gradio/preview": "0.13.1"
32
+ },
33
+ "peerDependencies": {
34
+ "svelte": "^4.0.0"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/gradio-app/gradio.git",
39
+ "directory": "js/simpletextbox"
40
+ }
41
+ }
src/frontend/tsconfig.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "checkJs": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "resolveJsonModule": true,
8
+ "skipLibCheck": true,
9
+ "sourceMap": true,
10
+ "strict": true,
11
+ "verbatimModuleSyntax": true
12
+ },
13
+ "exclude": ["node_modules", "dist", "./gradio.config.js"]
14
+ }
src/pyproject.toml ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = [
3
+ "hatchling",
4
+ "hatch-requirements-txt",
5
+ "hatch-fancy-pypi-readme>=22.5.0",
6
+ ]
7
+ build-backend = "hatchling.build"
8
+
9
+ [project]
10
+ name = "gradio_workflowbuilder"
11
+ version = "0.0.1"
12
+ description = "workflow builder"
13
+ readme = "README.md"
14
+ license = "apache-2.0"
15
+ requires-python = ">=3.10"
16
+ authors = [{ name = "YOUR NAME", email = "[email protected]" }]
17
+ keywords = ["gradio-custom-component", "gradio-template-SimpleTextbox", "workflow", "builder", "editor"]
18
+ # Add dependencies here
19
+ dependencies = ["gradio>=4.0,<6.0"]
20
+ classifiers = [
21
+ 'Development Status :: 3 - Alpha',
22
+ 'Operating System :: OS Independent',
23
+ 'Programming Language :: Python :: 3',
24
+ 'Programming Language :: Python :: 3 :: Only',
25
+ 'Programming Language :: Python :: 3.8',
26
+ 'Programming Language :: Python :: 3.9',
27
+ 'Programming Language :: Python :: 3.10',
28
+ 'Programming Language :: Python :: 3.11',
29
+ 'Topic :: Scientific/Engineering',
30
+ 'Topic :: Scientific/Engineering :: Artificial Intelligence',
31
+ 'Topic :: Scientific/Engineering :: Visualization',
32
+ ]
33
+
34
+ # The repository and space URLs are optional, but recommended.
35
+ # Adding a repository URL will create a badge in the auto-generated README that links to the repository.
36
+ # Adding a space URL will create a badge in the auto-generated README that links to the space.
37
+ # This will make it easy for people to find your deployed demo or source code when they
38
+ # encounter your project in the wild.
39
+
40
+ # [project.urls]
41
+ # repository = "your github repository"
42
+ # space = "your space url"
43
+
44
+ [project.optional-dependencies]
45
+ dev = ["build", "twine"]
46
+
47
+ [tool.hatch.build]
48
+ artifacts = ["/backend/gradio_workflowbuilder/templates", "*.pyi"]
49
+
50
+ [tool.hatch.build.targets.wheel]
51
+ packages = ["/backend/gradio_workflowbuilder"]