barokastor commited on
Commit
72d6111
Β·
verified Β·
1 Parent(s): b2d88d2

Upload folder using huggingface_hub

Browse files
__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
+ )
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_workflowbuilder
space.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from app import demo as app
3
+ import os
4
+
5
+ _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': []}}}
6
+
7
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
8
+
9
+ with gr.Blocks(
10
+ css=abs_path,
11
+ theme=gr.themes.Default(
12
+ font_mono=[
13
+ gr.themes.GoogleFont("Inconsolata"),
14
+ "monospace",
15
+ ],
16
+ ),
17
+ ) as demo:
18
+ gr.Markdown(
19
+ """
20
+ # `gradio_workflowbuilder`
21
+
22
+ <div style="display: flex; gap: 7px;">
23
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
24
+ </div>
25
+
26
+ workflow builder
27
+ """, elem_classes=["md-custom"], header_links=True)
28
+ app.render()
29
+ gr.Markdown(
30
+ """
31
+ ## Installation
32
+
33
+ pip install gradio_workflowbuilder
34
+ ## Usage
35
+
36
+ import gradio as gr
37
+ from gradio_workflowbuilder import WorkflowBuilder
38
+ import json
39
+
40
+
41
+ def export_workflow(workflow_data):
42
+ \"\"\"Export workflow as formatted JSON\"\"\"
43
+ if not workflow_data:
44
+ return "No workflow to export"
45
+ return json.dumps(workflow_data, indent=2)
46
+
47
+
48
+ # Create the main interface
49
+ with gr.Blocks(
50
+ title="🎨 Visual Workflow Builder",
51
+ theme=gr.themes.Soft(),
52
+ css=\"\"\"
53
+ .main-container { max-width: 1600px; margin: 0 auto; }
54
+ .workflow-section { margin-bottom: 2rem; }
55
+ .button-row { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
56
+
57
+ .component-description {
58
+ padding: 24px;
59
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
60
+ border-radius: 12px;
61
+ border-left: 4px solid #3b82f6;
62
+ margin: 16px 0;
63
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
64
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
65
+ }
66
+
67
+ .component-description p {
68
+ margin: 10px 0;
69
+ line-height: 1.6;
70
+ color: #374151;
71
+ }
72
+
73
+ .base-description {
74
+ font-size: 17px;
75
+ font-weight: 600;
76
+ color: #1e293b;
77
+ }
78
+
79
+ .base-description strong {
80
+ color: #3b82f6;
81
+ font-weight: 700;
82
+ }
83
+
84
+ .feature-description {
85
+ font-size: 16px;
86
+ color: #1e293b;
87
+ font-weight: 500;
88
+ }
89
+
90
+ .customization-note {
91
+ font-size: 15px;
92
+ color: #64748b;
93
+ font-style: italic;
94
+ }
95
+
96
+ .sample-intro {
97
+ font-size: 15px;
98
+ color: #1e293b;
99
+ font-weight: 600;
100
+ margin-top: 16px;
101
+ border-top: 1px solid #e2e8f0;
102
+ padding-top: 16px;
103
+ }
104
+ \"\"\"
105
+ ) as demo:
106
+
107
+ with gr.Column(elem_classes=["main-container"]):
108
+ gr.Markdown(\"\"\"
109
+ # 🎨 Visual Workflow Builder
110
+
111
+ **Create sophisticated workflows with drag and drop simplicity.**
112
+ \"\"\")
113
+
114
+ # Simple description section with styling
115
+ gr.HTML(\"\"\"
116
+ <div class="component-description">
117
+ <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
118
+ <p class="feature-description">Create custom workflows.</p>
119
+ <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
120
+ <p class="sample-intro">Here is a sample:</p>
121
+ </div>
122
+ \"\"\")
123
+
124
+ # Main workflow builder section
125
+ with gr.Column(elem_classes=["workflow-section"]):
126
+ workflow_builder = WorkflowBuilder(
127
+ label="🎨 Visual Workflow Designer",
128
+ info="Drag components from the sidebar β†’ Connect nodes by dragging from output (right) to input (left) β†’ Click nodes to edit properties"
129
+ )
130
+
131
+ # Export section below the workflow
132
+ gr.Markdown("## πŸ’Ύ Export Workflow")
133
+
134
+ with gr.Row():
135
+ with gr.Column():
136
+ export_output = gr.Code(
137
+ language="json",
138
+ label="Workflow Configuration",
139
+ lines=10
140
+ )
141
+
142
+ # Action button
143
+ with gr.Row(elem_classes=["button-row"]):
144
+ export_btn = gr.Button("πŸ’Ύ Export JSON", variant="primary", size="lg")
145
+
146
+ # Instructions
147
+ with gr.Accordion("πŸ“– How to Use", open=False):
148
+ gr.Markdown(\"\"\"
149
+ ### πŸš€ Getting Started
150
+
151
+ 1. **Add Components**: Drag items from the left sidebar onto the canvas
152
+ 2. **Connect Nodes**: Drag from the blue circle on the right of a node to the left circle of another
153
+ 3. **Edit Properties**: Click any node to see its editable properties on the right panel
154
+ 4. **Organize**: Drag nodes around to create a clean workflow layout
155
+ 5. **Delete**: Use the βœ• button on nodes or click the red circle on connections
156
+
157
+ ### 🎯 Component Types
158
+
159
+ - **πŸ“₯ Inputs**: Text fields, file uploads, number inputs
160
+ - **βš™οΈ Processing**: Language models, text processors, conditionals
161
+ - **πŸ“€ Outputs**: Text displays, file exports, charts
162
+ - **πŸ”§ Tools**: API calls, data transformers, validators
163
+
164
+ ### πŸ’‘ Pro Tips
165
+
166
+ - **Collapsible Panels**: Use the arrow buttons to hide/show sidebars
167
+ - **Live Updates**: Properties update in real-time as you edit
168
+ - **Export Options**: Get JSON config for your workflow
169
+ \"\"\")
170
+
171
+ # Event handlers
172
+ export_btn.click(
173
+ fn=export_workflow,
174
+ inputs=[workflow_builder],
175
+ outputs=[export_output]
176
+ )
177
+
178
+
179
+ if __name__ == "__main__":
180
+ demo.launch(
181
+ server_name="0.0.0.0",
182
+ show_error=True
183
+ )
184
+
185
+ """, elem_classes=["md-custom"], header_links=True)
186
+
187
+
188
+ gr.Markdown("""
189
+ ## `WorkflowBuilder`
190
+
191
+ ### Initialization
192
+ """, elem_classes=["md-custom"], header_links=True)
193
+
194
+ gr.ParamViewer(value=_docs["WorkflowBuilder"]["members"]["__init__"], linkify=[])
195
+
196
+
197
+ gr.Markdown("### Events")
198
+ gr.ParamViewer(value=_docs["WorkflowBuilder"]["events"], linkify=['Event'])
199
+
200
+
201
+
202
+
203
+ gr.Markdown("""
204
+
205
+ ### User function
206
+
207
+ 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).
208
+
209
+ - When used as an Input, the component only impacts the input signature of the user function.
210
+ - When used as an output, the component only impacts the return signature of the user function.
211
+
212
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
213
+
214
+
215
+
216
+ ```python
217
+ def predict(
218
+ value: typing.Dict[str, typing.Any][str, typing.Any]
219
+ ) -> typing.Dict[str, typing.Any][str, typing.Any]:
220
+ return value
221
+ """, elem_classes=["md-custom", "WorkflowBuilder-user-fn"], header_links=True)
222
+
223
+
224
+
225
+
226
+ demo.load(None, js=r"""function() {
227
+ const refs = {};
228
+ const user_fn_refs = {
229
+ WorkflowBuilder: [], };
230
+ requestAnimationFrame(() => {
231
+
232
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
233
+ if (refs.length > 0) {
234
+ const el = document.querySelector(`.${key}-user-fn`);
235
+ if (!el) return;
236
+ refs.forEach(ref => {
237
+ el.innerHTML = el.innerHTML.replace(
238
+ new RegExp("\\b"+ref+"\\b", "g"),
239
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
240
+ );
241
+ })
242
+ }
243
+ })
244
+
245
+ Object.entries(refs).forEach(([key, refs]) => {
246
+ if (refs.length > 0) {
247
+ const el = document.querySelector(`.${key}`);
248
+ if (!el) return;
249
+ refs.forEach(ref => {
250
+ el.innerHTML = el.innerHTML.replace(
251
+ new RegExp("\\b"+ref+"\\b", "g"),
252
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
253
+ );
254
+ })
255
+ }
256
+ })
257
+ })
258
+ }
259
+
260
+ """)
261
+
262
+ demo.launch()
src/backend/gradio_workflowbuilder/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .workflowbuilder import WorkflowBuilder
2
+
3
+ __all__ = ['WorkflowBuilder']
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/requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio_workflowbuilder
src/demo/space.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from app import demo as app
3
+ import os
4
+
5
+ _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': []}}}
6
+
7
+ abs_path = os.path.join(os.path.dirname(__file__), "css.css")
8
+
9
+ with gr.Blocks(
10
+ css=abs_path,
11
+ theme=gr.themes.Default(
12
+ font_mono=[
13
+ gr.themes.GoogleFont("Inconsolata"),
14
+ "monospace",
15
+ ],
16
+ ),
17
+ ) as demo:
18
+ gr.Markdown(
19
+ """
20
+ # `gradio_workflowbuilder`
21
+
22
+ <div style="display: flex; gap: 7px;">
23
+ <img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
24
+ </div>
25
+
26
+ workflow builder
27
+ """, elem_classes=["md-custom"], header_links=True)
28
+ app.render()
29
+ gr.Markdown(
30
+ """
31
+ ## Installation
32
+
33
+ pip install gradio_workflowbuilder
34
+ ## Usage
35
+
36
+ import gradio as gr
37
+ from gradio_workflowbuilder import WorkflowBuilder
38
+ import json
39
+
40
+
41
+ def export_workflow(workflow_data):
42
+ \"\"\"Export workflow as formatted JSON\"\"\"
43
+ if not workflow_data:
44
+ return "No workflow to export"
45
+ return json.dumps(workflow_data, indent=2)
46
+
47
+
48
+ # Create the main interface
49
+ with gr.Blocks(
50
+ title="🎨 Visual Workflow Builder",
51
+ theme=gr.themes.Soft(),
52
+ css=\"\"\"
53
+ .main-container { max-width: 1600px; margin: 0 auto; }
54
+ .workflow-section { margin-bottom: 2rem; }
55
+ .button-row { display: flex; gap: 1rem; justify-content: center; margin: 1rem 0; }
56
+
57
+ .component-description {
58
+ padding: 24px;
59
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
60
+ border-radius: 12px;
61
+ border-left: 4px solid #3b82f6;
62
+ margin: 16px 0;
63
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
64
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
65
+ }
66
+
67
+ .component-description p {
68
+ margin: 10px 0;
69
+ line-height: 1.6;
70
+ color: #374151;
71
+ }
72
+
73
+ .base-description {
74
+ font-size: 17px;
75
+ font-weight: 600;
76
+ color: #1e293b;
77
+ }
78
+
79
+ .base-description strong {
80
+ color: #3b82f6;
81
+ font-weight: 700;
82
+ }
83
+
84
+ .feature-description {
85
+ font-size: 16px;
86
+ color: #1e293b;
87
+ font-weight: 500;
88
+ }
89
+
90
+ .customization-note {
91
+ font-size: 15px;
92
+ color: #64748b;
93
+ font-style: italic;
94
+ }
95
+
96
+ .sample-intro {
97
+ font-size: 15px;
98
+ color: #1e293b;
99
+ font-weight: 600;
100
+ margin-top: 16px;
101
+ border-top: 1px solid #e2e8f0;
102
+ padding-top: 16px;
103
+ }
104
+ \"\"\"
105
+ ) as demo:
106
+
107
+ with gr.Column(elem_classes=["main-container"]):
108
+ gr.Markdown(\"\"\"
109
+ # 🎨 Visual Workflow Builder
110
+
111
+ **Create sophisticated workflows with drag and drop simplicity.**
112
+ \"\"\")
113
+
114
+ # Simple description section with styling
115
+ gr.HTML(\"\"\"
116
+ <div class="component-description">
117
+ <p class="base-description">Base custom component powered by <strong>svelteflow</strong>.</p>
118
+ <p class="feature-description">Create custom workflows.</p>
119
+ <p class="customization-note">You can customise the nodes as well as the design of the workflow.</p>
120
+ <p class="sample-intro">Here is a sample:</p>
121
+ </div>
122
+ \"\"\")
123
+
124
+ # Main workflow builder section
125
+ with gr.Column(elem_classes=["workflow-section"]):
126
+ workflow_builder = WorkflowBuilder(
127
+ label="🎨 Visual Workflow Designer",
128
+ info="Drag components from the sidebar β†’ Connect nodes by dragging from output (right) to input (left) β†’ Click nodes to edit properties"
129
+ )
130
+
131
+ # Export section below the workflow
132
+ gr.Markdown("## πŸ’Ύ Export Workflow")
133
+
134
+ with gr.Row():
135
+ with gr.Column():
136
+ export_output = gr.Code(
137
+ language="json",
138
+ label="Workflow Configuration",
139
+ lines=10
140
+ )
141
+
142
+ # Action button
143
+ with gr.Row(elem_classes=["button-row"]):
144
+ export_btn = gr.Button("πŸ’Ύ Export JSON", variant="primary", size="lg")
145
+
146
+ # Instructions
147
+ with gr.Accordion("πŸ“– How to Use", open=False):
148
+ gr.Markdown(\"\"\"
149
+ ### πŸš€ Getting Started
150
+
151
+ 1. **Add Components**: Drag items from the left sidebar onto the canvas
152
+ 2. **Connect Nodes**: Drag from the blue circle on the right of a node to the left circle of another
153
+ 3. **Edit Properties**: Click any node to see its editable properties on the right panel
154
+ 4. **Organize**: Drag nodes around to create a clean workflow layout
155
+ 5. **Delete**: Use the βœ• button on nodes or click the red circle on connections
156
+
157
+ ### 🎯 Component Types
158
+
159
+ - **πŸ“₯ Inputs**: Text fields, file uploads, number inputs
160
+ - **βš™οΈ Processing**: Language models, text processors, conditionals
161
+ - **πŸ“€ Outputs**: Text displays, file exports, charts
162
+ - **πŸ”§ Tools**: API calls, data transformers, validators
163
+
164
+ ### πŸ’‘ Pro Tips
165
+
166
+ - **Collapsible Panels**: Use the arrow buttons to hide/show sidebars
167
+ - **Live Updates**: Properties update in real-time as you edit
168
+ - **Export Options**: Get JSON config for your workflow
169
+ \"\"\")
170
+
171
+ # Event handlers
172
+ export_btn.click(
173
+ fn=export_workflow,
174
+ inputs=[workflow_builder],
175
+ outputs=[export_output]
176
+ )
177
+
178
+
179
+ if __name__ == "__main__":
180
+ demo.launch(
181
+ server_name="0.0.0.0",
182
+ show_error=True
183
+ )
184
+
185
+ """, elem_classes=["md-custom"], header_links=True)
186
+
187
+
188
+ gr.Markdown("""
189
+ ## `WorkflowBuilder`
190
+
191
+ ### Initialization
192
+ """, elem_classes=["md-custom"], header_links=True)
193
+
194
+ gr.ParamViewer(value=_docs["WorkflowBuilder"]["members"]["__init__"], linkify=[])
195
+
196
+
197
+ gr.Markdown("### Events")
198
+ gr.ParamViewer(value=_docs["WorkflowBuilder"]["events"], linkify=['Event'])
199
+
200
+
201
+
202
+
203
+ gr.Markdown("""
204
+
205
+ ### User function
206
+
207
+ 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).
208
+
209
+ - When used as an Input, the component only impacts the input signature of the user function.
210
+ - When used as an output, the component only impacts the return signature of the user function.
211
+
212
+ The code snippet below is accurate in cases where the component is used as both an input and an output.
213
+
214
+
215
+
216
+ ```python
217
+ def predict(
218
+ value: typing.Dict[str, typing.Any][str, typing.Any]
219
+ ) -> typing.Dict[str, typing.Any][str, typing.Any]:
220
+ return value
221
+ """, elem_classes=["md-custom", "WorkflowBuilder-user-fn"], header_links=True)
222
+
223
+
224
+
225
+
226
+ demo.load(None, js=r"""function() {
227
+ const refs = {};
228
+ const user_fn_refs = {
229
+ WorkflowBuilder: [], };
230
+ requestAnimationFrame(() => {
231
+
232
+ Object.entries(user_fn_refs).forEach(([key, refs]) => {
233
+ if (refs.length > 0) {
234
+ const el = document.querySelector(`.${key}-user-fn`);
235
+ if (!el) return;
236
+ refs.forEach(ref => {
237
+ el.innerHTML = el.innerHTML.replace(
238
+ new RegExp("\\b"+ref+"\\b", "g"),
239
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
240
+ );
241
+ })
242
+ }
243
+ })
244
+
245
+ Object.entries(refs).forEach(([key, refs]) => {
246
+ if (refs.length > 0) {
247
+ const el = document.querySelector(`.${key}`);
248
+ if (!el) return;
249
+ refs.forEach(ref => {
250
+ el.innerHTML = el.innerHTML.replace(
251
+ new RegExp("\\b"+ref+"\\b", "g"),
252
+ `<a href="#h-${ref.toLowerCase()}">${ref}</a>`
253
+ );
254
+ })
255
+ }
256
+ })
257
+ })
258
+ }
259
+
260
+ """)
261
+
262
+ demo.launch()