Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
app.py ββββββββββββββββββββββββββββββββββββββββββββββββ
|
3 |
β Preview Β· Save Β· Load κΈ°λ₯ ν¬ν¨
|
4 |
β Load μ λ
Έλ/μ’ν/μ£μ§ μλ 볡μ
|
5 |
-
β
|
6 |
"""
|
7 |
|
8 |
import os, json, typing, tempfile
|
@@ -23,32 +23,32 @@ def export_file(data: typing.Dict[str, typing.Any]) -> typing.Optional[str]:
|
|
23 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
24 |
return path
|
25 |
|
26 |
-
def
|
27 |
-
"""
|
28 |
-
μ
λ‘λλ JSON β WorkflowBuilder κ° & μ½λλ·° κ°±μ .
|
29 |
-
WorkflowBuilderκ° μ§μ dictλ₯Ό λ°λλ‘ μμ
|
30 |
-
"""
|
31 |
if file_obj is None:
|
32 |
-
return
|
33 |
|
34 |
try:
|
35 |
with open(file_obj.name, "r", encoding="utf-8") as f:
|
36 |
data = json.load(f)
|
37 |
-
|
38 |
-
# WorkflowBuilderκ° κΈ°λνλ νμμΈμ§ νμΈ
|
39 |
-
if not isinstance(data, dict):
|
40 |
-
return {}, "Invalid workflow format: expected dictionary"
|
41 |
-
|
42 |
-
# νμ ν€λ€μ΄ μλμ§ νμΈ (nodes, edges λ±)
|
43 |
-
if 'nodes' not in data:
|
44 |
-
data['nodes'] = []
|
45 |
-
if 'edges' not in data:
|
46 |
-
data['edges'] = []
|
47 |
-
|
48 |
-
return data, json.dumps(data, indent=2, ensure_ascii=False)
|
49 |
-
|
50 |
except Exception as e:
|
51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
# -------------------------------------------------------------------
|
54 |
# π¨ CSS
|
@@ -70,12 +70,15 @@ CSS = """
|
|
70 |
.customization-note{font-size:15px;font-style:italic;color:#64748b;}
|
71 |
.sample-intro{font-size:15px;font-weight:600;color:#1e293b;margin-top:16px;
|
72 |
border-top:1px solid #e2e8f0;padding-top:16px;}
|
|
|
73 |
"""
|
74 |
|
75 |
# -------------------------------------------------------------------
|
76 |
# π₯οΈ Gradio μ±
|
77 |
# -------------------------------------------------------------------
|
78 |
with gr.Blocks(title="π¨ Visual Workflow Builder", theme=gr.themes.Soft(), css=CSS) as demo:
|
|
|
|
|
79 |
|
80 |
with gr.Column(elem_classes=["main-container"]):
|
81 |
gr.Markdown("# π¨ Visual Workflow Builder\n**Create sophisticated workflows with drag-and-drop simplicity.**")
|
@@ -96,7 +99,7 @@ with gr.Blocks(title="π¨ Visual Workflow Builder", theme=gr.themes.Soft(), css
|
|
96 |
workflow_builder = WorkflowBuilder(
|
97 |
label="π¨ Visual Workflow Designer",
|
98 |
info="Drag from output β input β’ Click nodes to edit properties",
|
99 |
-
value={"nodes": [], "edges": []}
|
100 |
)
|
101 |
|
102 |
# βββ Export / Import βββ
|
@@ -105,114 +108,213 @@ with gr.Blocks(title="π¨ Visual Workflow Builder", theme=gr.themes.Soft(), css
|
|
105 |
with gr.Row(elem_classes=["button-row"]):
|
106 |
btn_preview = gr.Button("π Preview JSON")
|
107 |
btn_download = gr.DownloadButton("πΎ Save JSON file")
|
108 |
-
|
|
|
|
|
|
|
109 |
|
110 |
code_view = gr.Code(language="json", label="Workflow Configuration", lines=14)
|
111 |
|
112 |
-
#
|
113 |
-
|
|
|
|
|
114 |
|
115 |
-
# μ΄λ²€νΈ μ°κ²°
|
116 |
-
btn_preview.click(fn=export_pretty, inputs=workflow_builder, outputs=code_view)
|
117 |
-
btn_download.click(fn=export_file, inputs=workflow_builder)
|
118 |
|
119 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
file_upload.change(
|
121 |
-
fn=
|
122 |
-
inputs=file_upload,
|
123 |
-
outputs=
|
|
|
|
|
|
|
|
|
124 |
)
|
125 |
-
|
126 |
-
# λλ²κΉ
μ© - νμ¬ workflow μν νμΈ
|
127 |
-
def debug_workflow(data):
|
128 |
-
print("Current workflow data:", json.dumps(data, indent=2))
|
129 |
-
return f"Nodes: {len(data.get('nodes', []))}, Edges: {len(data.get('edges', []))}"
|
130 |
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
"position": {"x": 100, "y": 100},
|
152 |
-
"data": {"label": "Node 1"}
|
153 |
-
}
|
154 |
-
],
|
155 |
-
"edges": [
|
156 |
-
{
|
157 |
-
"id": "edge_1",
|
158 |
-
"source": "node_1",
|
159 |
-
"target": "node_2"
|
160 |
-
}
|
161 |
-
]
|
162 |
-
}
|
163 |
-
```
|
164 |
-
"""
|
165 |
-
)
|
166 |
|
167 |
-
# μν μν¬νλ‘μ°
|
168 |
gr.Markdown("### π― Sample Workflows")
|
169 |
|
170 |
-
def
|
171 |
-
|
172 |
"nodes": [
|
173 |
{
|
174 |
-
"id": "
|
175 |
-
"type": "
|
176 |
"position": {"x": 100, "y": 100},
|
177 |
-
"data": {"label": "Input Node", "value": "
|
178 |
},
|
179 |
{
|
180 |
-
"id": "
|
181 |
-
"type": "
|
182 |
"position": {"x": 300, "y": 100},
|
183 |
-
"data": {"label": "Process Node"}
|
184 |
},
|
185 |
{
|
186 |
-
"id": "
|
187 |
-
"type": "
|
188 |
"position": {"x": 500, "y": 100},
|
189 |
-
"data": {"label": "Output Node"}
|
190 |
}
|
191 |
],
|
192 |
"edges": [
|
193 |
{
|
194 |
"id": "edge_1",
|
195 |
-
"source": "
|
196 |
-
"target": "
|
197 |
-
"
|
198 |
-
"targetHandle": "input"
|
199 |
},
|
200 |
{
|
201 |
"id": "edge_2",
|
202 |
-
"source": "
|
203 |
-
"target": "
|
204 |
-
"
|
205 |
-
"targetHandle": "input"
|
206 |
}
|
207 |
]
|
208 |
}
|
209 |
-
return sample, json.dumps(sample, indent=2, ensure_ascii=False)
|
210 |
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
216 |
|
217 |
# -------------------------------------------------------------------
|
218 |
# π μ€ν
|
|
|
2 |
app.py ββββββββββββββββββββββββββββββββββββββββββββββββ
|
3 |
β Preview Β· Save Β· Load κΈ°λ₯ ν¬ν¨
|
4 |
β Load μ λ
Έλ/μ’ν/μ£μ§ μλ 볡μ
|
5 |
+
β Stateλ₯Ό μ¬μ©ν μμ μ μΈ JSON λ‘λ
|
6 |
"""
|
7 |
|
8 |
import os, json, typing, tempfile
|
|
|
23 |
json.dump(data, f, ensure_ascii=False, indent=2)
|
24 |
return path
|
25 |
|
26 |
+
def load_workflow_from_file(file_obj):
|
27 |
+
"""JSON νμΌμ μ½μ΄μ λ°μ΄ν°λ§ λ°ν"""
|
|
|
|
|
|
|
28 |
if file_obj is None:
|
29 |
+
return None
|
30 |
|
31 |
try:
|
32 |
with open(file_obj.name, "r", encoding="utf-8") as f:
|
33 |
data = json.load(f)
|
34 |
+
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
except Exception as e:
|
36 |
+
print(f"Error loading file: {e}")
|
37 |
+
return None
|
38 |
+
|
39 |
+
def update_workflow_display(workflow_data):
|
40 |
+
"""μν¬νλ‘μ° λ°μ΄ν°λ₯Ό μ½λλ·°μ νμ"""
|
41 |
+
if workflow_data:
|
42 |
+
return json.dumps(workflow_data, indent=2, ensure_ascii=False)
|
43 |
+
return "No workflow loaded"
|
44 |
+
|
45 |
+
def debug_workflow(data):
|
46 |
+
"""νμ¬ μν¬νλ‘μ° μν λλ²κΉ
"""
|
47 |
+
if not data:
|
48 |
+
return "No workflow data"
|
49 |
+
nodes_count = len(data.get('nodes', []))
|
50 |
+
edges_count = len(data.get('edges', []))
|
51 |
+
return f"Nodes: {nodes_count}, Edges: {edges_count}"
|
52 |
|
53 |
# -------------------------------------------------------------------
|
54 |
# π¨ CSS
|
|
|
70 |
.customization-note{font-size:15px;font-style:italic;color:#64748b;}
|
71 |
.sample-intro{font-size:15px;font-weight:600;color:#1e293b;margin-top:16px;
|
72 |
border-top:1px solid #e2e8f0;padding-top:16px;}
|
73 |
+
.load-button{background:#10b981 !important;}
|
74 |
"""
|
75 |
|
76 |
# -------------------------------------------------------------------
|
77 |
# π₯οΈ Gradio μ±
|
78 |
# -------------------------------------------------------------------
|
79 |
with gr.Blocks(title="π¨ Visual Workflow Builder", theme=gr.themes.Soft(), css=CSS) as demo:
|
80 |
+
# Stateλ‘ μν¬νλ‘μ° λ°μ΄ν° κ΄λ¦¬
|
81 |
+
workflow_state = gr.State(value={"nodes": [], "edges": []})
|
82 |
|
83 |
with gr.Column(elem_classes=["main-container"]):
|
84 |
gr.Markdown("# π¨ Visual Workflow Builder\n**Create sophisticated workflows with drag-and-drop simplicity.**")
|
|
|
99 |
workflow_builder = WorkflowBuilder(
|
100 |
label="π¨ Visual Workflow Designer",
|
101 |
info="Drag from output β input β’ Click nodes to edit properties",
|
102 |
+
value={"nodes": [], "edges": []}
|
103 |
)
|
104 |
|
105 |
# βββ Export / Import βββ
|
|
|
108 |
with gr.Row(elem_classes=["button-row"]):
|
109 |
btn_preview = gr.Button("π Preview JSON")
|
110 |
btn_download = gr.DownloadButton("πΎ Save JSON file")
|
111 |
+
|
112 |
+
with gr.Row(elem_classes=["button-row"]):
|
113 |
+
file_upload = gr.File(label="π Select JSON file", file_types=[".json"])
|
114 |
+
btn_load = gr.Button("π₯ Load into Workflow", variant="primary", elem_classes=["load-button"])
|
115 |
|
116 |
code_view = gr.Code(language="json", label="Workflow Configuration", lines=14)
|
117 |
|
118 |
+
# λλ²κΉ
μΉμ
|
119 |
+
with gr.Row():
|
120 |
+
btn_debug = gr.Button("π Debug Current State", variant="secondary")
|
121 |
+
debug_output = gr.Textbox(label="Debug Info", lines=1)
|
122 |
|
123 |
+
# βββ μ΄λ²€νΈ μ°κ²° βββ
|
|
|
|
|
124 |
|
125 |
+
# Preview λ²νΌ - νμ¬ μν¬νλ‘μ°λ₯Ό JSONμΌλ‘ νμ
|
126 |
+
btn_preview.click(
|
127 |
+
fn=export_pretty,
|
128 |
+
inputs=workflow_builder,
|
129 |
+
outputs=code_view
|
130 |
+
)
|
131 |
+
|
132 |
+
# Download λ²νΌ
|
133 |
+
btn_download.click(
|
134 |
+
fn=export_file,
|
135 |
+
inputs=workflow_builder
|
136 |
+
)
|
137 |
+
|
138 |
+
# νμΌ μ
λ‘λ -> Stateμ μ μ₯
|
139 |
file_upload.change(
|
140 |
+
fn=load_workflow_from_file,
|
141 |
+
inputs=file_upload,
|
142 |
+
outputs=workflow_state
|
143 |
+
).then(
|
144 |
+
fn=update_workflow_display,
|
145 |
+
inputs=workflow_state,
|
146 |
+
outputs=code_view
|
147 |
)
|
|
|
|
|
|
|
|
|
|
|
148 |
|
149 |
+
# Load λ²νΌ ν΄λ¦ -> Stateμμ WorkflowBuilderλ‘ μ λ¬
|
150 |
+
btn_load.click(
|
151 |
+
fn=lambda x: x, # State κ°μ κ·Έλλ‘ μ λ¬
|
152 |
+
inputs=workflow_state,
|
153 |
+
outputs=workflow_builder
|
154 |
+
)
|
155 |
+
|
156 |
+
# Debug λ²νΌ
|
157 |
+
btn_debug.click(
|
158 |
+
fn=debug_workflow,
|
159 |
+
inputs=workflow_builder,
|
160 |
+
outputs=debug_output
|
161 |
+
)
|
162 |
+
|
163 |
+
# WorkflowBuilder κ° λ³κ²½ μ State μ
λ°μ΄νΈ
|
164 |
+
workflow_builder.change(
|
165 |
+
fn=lambda x: x,
|
166 |
+
inputs=workflow_builder,
|
167 |
+
outputs=workflow_state
|
168 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
|
170 |
+
# βββ μν μν¬νλ‘μ° βββ
|
171 |
gr.Markdown("### π― Sample Workflows")
|
172 |
|
173 |
+
def create_sample_workflow():
|
174 |
+
return {
|
175 |
"nodes": [
|
176 |
{
|
177 |
+
"id": "node_1",
|
178 |
+
"type": "custom",
|
179 |
"position": {"x": 100, "y": 100},
|
180 |
+
"data": {"label": "Input Node", "value": "Start"}
|
181 |
},
|
182 |
{
|
183 |
+
"id": "node_2",
|
184 |
+
"type": "custom",
|
185 |
"position": {"x": 300, "y": 100},
|
186 |
+
"data": {"label": "Process Node", "value": "Transform"}
|
187 |
},
|
188 |
{
|
189 |
+
"id": "node_3",
|
190 |
+
"type": "custom",
|
191 |
"position": {"x": 500, "y": 100},
|
192 |
+
"data": {"label": "Output Node", "value": "Result"}
|
193 |
}
|
194 |
],
|
195 |
"edges": [
|
196 |
{
|
197 |
"id": "edge_1",
|
198 |
+
"source": "node_1",
|
199 |
+
"target": "node_2",
|
200 |
+
"type": "default"
|
|
|
201 |
},
|
202 |
{
|
203 |
"id": "edge_2",
|
204 |
+
"source": "node_2",
|
205 |
+
"target": "node_3",
|
206 |
+
"type": "default"
|
|
|
207 |
}
|
208 |
]
|
209 |
}
|
|
|
210 |
|
211 |
+
with gr.Row():
|
212 |
+
btn_sample1 = gr.Button("π Load Linear Workflow", variant="primary")
|
213 |
+
btn_sample2 = gr.Button("π Load Branching Workflow", variant="primary")
|
214 |
+
|
215 |
+
def create_branching_workflow():
|
216 |
+
return {
|
217 |
+
"nodes": [
|
218 |
+
{
|
219 |
+
"id": "start",
|
220 |
+
"type": "custom",
|
221 |
+
"position": {"x": 100, "y": 200},
|
222 |
+
"data": {"label": "Start", "value": "Input Data"}
|
223 |
+
},
|
224 |
+
{
|
225 |
+
"id": "branch1",
|
226 |
+
"type": "custom",
|
227 |
+
"position": {"x": 300, "y": 100},
|
228 |
+
"data": {"label": "Branch A", "value": "Process A"}
|
229 |
+
},
|
230 |
+
{
|
231 |
+
"id": "branch2",
|
232 |
+
"type": "custom",
|
233 |
+
"position": {"x": 300, "y": 300},
|
234 |
+
"data": {"label": "Branch B", "value": "Process B"}
|
235 |
+
},
|
236 |
+
{
|
237 |
+
"id": "merge",
|
238 |
+
"type": "custom",
|
239 |
+
"position": {"x": 500, "y": 200},
|
240 |
+
"data": {"label": "Merge", "value": "Combine Results"}
|
241 |
+
}
|
242 |
+
],
|
243 |
+
"edges": [
|
244 |
+
{"id": "e1", "source": "start", "target": "branch1", "type": "default"},
|
245 |
+
{"id": "e2", "source": "start", "target": "branch2", "type": "default"},
|
246 |
+
{"id": "e3", "source": "branch1", "target": "merge", "type": "default"},
|
247 |
+
{"id": "e4", "source": "branch2", "target": "merge", "type": "default"}
|
248 |
+
]
|
249 |
+
}
|
250 |
+
|
251 |
+
# μν λ‘λ μ΄λ²€νΈ
|
252 |
+
btn_sample1.click(
|
253 |
+
fn=create_sample_workflow,
|
254 |
+
outputs=workflow_state
|
255 |
+
).then(
|
256 |
+
fn=lambda x: x,
|
257 |
+
inputs=workflow_state,
|
258 |
+
outputs=workflow_builder
|
259 |
+
).then(
|
260 |
+
fn=update_workflow_display,
|
261 |
+
inputs=workflow_state,
|
262 |
+
outputs=code_view
|
263 |
)
|
264 |
+
|
265 |
+
btn_sample2.click(
|
266 |
+
fn=create_branching_workflow,
|
267 |
+
outputs=workflow_state
|
268 |
+
).then(
|
269 |
+
fn=lambda x: x,
|
270 |
+
inputs=workflow_state,
|
271 |
+
outputs=workflow_builder
|
272 |
+
).then(
|
273 |
+
fn=update_workflow_display,
|
274 |
+
inputs=workflow_state,
|
275 |
+
outputs=code_view
|
276 |
+
)
|
277 |
+
|
278 |
+
# βββ μ¬μ© λ°©λ² βββ
|
279 |
+
with gr.Accordion("π How to Use", open=False):
|
280 |
+
gr.Markdown(
|
281 |
+
"""
|
282 |
+
### π Quick Start
|
283 |
+
1. **Create Workflow** β Add nodes and connect them
|
284 |
+
2. **Save** β Click "Save JSON file" to download
|
285 |
+
3. **Load** β Select file β Click "Load into Workflow"
|
286 |
+
|
287 |
+
### π Loading Workflows
|
288 |
+
1. Click "Select JSON file" and choose your workflow file
|
289 |
+
2. Click the green "Load into Workflow" button
|
290 |
+
3. Your workflow will appear in the visual designer
|
291 |
+
|
292 |
+
### π― Try Sample Workflows
|
293 |
+
- Click sample buttons to see example workflows
|
294 |
+
- Modify them to understand the structure
|
295 |
+
|
296 |
+
### π JSON Structure
|
297 |
+
```json
|
298 |
+
{
|
299 |
+
"nodes": [
|
300 |
+
{
|
301 |
+
"id": "unique_id",
|
302 |
+
"type": "custom",
|
303 |
+
"position": {"x": 100, "y": 100},
|
304 |
+
"data": {"label": "Node Name"}
|
305 |
+
}
|
306 |
+
],
|
307 |
+
"edges": [
|
308 |
+
{
|
309 |
+
"id": "edge_id",
|
310 |
+
"source": "node_id_1",
|
311 |
+
"target": "node_id_2"
|
312 |
+
}
|
313 |
+
]
|
314 |
+
}
|
315 |
+
```
|
316 |
+
"""
|
317 |
+
)
|
318 |
|
319 |
# -------------------------------------------------------------------
|
320 |
# π μ€ν
|