fantaxy commited on
Commit
43f50cc
Β·
verified Β·
1 Parent(s): 101f725

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -97
app.py CHANGED
@@ -2,7 +2,7 @@
2
  app.py ――――――――――――――――――――――――――――――――――――――――――――――――
3
  βœ“ Preview Β· Save Β· Load κΈ°λŠ₯ 포함
4
  βœ“ Load μ‹œ λ…Έλ“œ/μ’Œν‘œ/μ—£μ§€ μžλ™ 볡원
5
- βœ“ JSON λ‘œλ“œ μ‹œ μ‹œκ°μ  λ…Έλ“œ μžλ™ 배치 μˆ˜μ •
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 import_workflow(file_obj: gr.File) -> typing.Tuple[typing.Dict[str, typing.Any], str]:
27
- """
28
- μ—…λ‘œλ“œλœ JSON β†’ WorkflowBuilder κ°’ & μ½”λ“œλ·° κ°±μ‹ .
29
- WorkflowBuilderκ°€ 직접 dictλ₯Ό 받도둝 μˆ˜μ •
30
- """
31
  if file_obj is None:
32
- return {}, "No workflow loaded"
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
- return {}, f"Error loading workflow: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- file_upload = gr.File(label="πŸ“‚ Load JSON", file_types=[".json"])
 
 
 
109
 
110
  code_view = gr.Code(language="json", label="Workflow Configuration", lines=14)
111
 
112
- # μƒνƒœ λ©”μ‹œμ§€ ν‘œμ‹œμš©
113
- status_msg = gr.Textbox(label="Status", visible=False)
 
 
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
- # file_upload 이벀트 μˆ˜μ • - WorkflowBuilderλ₯Ό 직접 μ—…λ°μ΄νŠΈ
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  file_upload.change(
121
- fn=import_workflow,
122
- inputs=file_upload,
123
- outputs=[workflow_builder, code_view]
 
 
 
 
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
- btn_debug = gr.Button("πŸ” Debug Current State", variant="secondary")
132
- debug_output = gr.Textbox(label="Debug Info", lines=1)
133
- btn_debug.click(fn=debug_workflow, inputs=workflow_builder, outputs=debug_output)
134
-
135
- with gr.Accordion("πŸ“– How to Use", open=False):
136
- gr.Markdown(
137
- """
138
- 1. **Add Nodes** β€” drag items from the sidebar onto the canvas
139
- 2. **Connect** β€” blue handle (output) ➜ gray handle (input)
140
- 3. **Edit** β€” click any node to change its properties
141
- 4. **Save** β€” *Save JSON file* to download your workflow
142
- 5. **Load** β€” *Load JSON* to restore (nodes auto-position)
143
-
144
- ### πŸ“ Expected JSON Format:
145
- ```json
146
- {
147
- "nodes": [
148
- {
149
- "id": "node_1",
150
- "type": "custom",
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 load_sample_workflow():
171
- sample = {
172
  "nodes": [
173
  {
174
- "id": "input_1",
175
- "type": "input",
176
  "position": {"x": 100, "y": 100},
177
- "data": {"label": "Input Node", "value": "Sample Input"}
178
  },
179
  {
180
- "id": "process_1",
181
- "type": "default",
182
  "position": {"x": 300, "y": 100},
183
- "data": {"label": "Process Node"}
184
  },
185
  {
186
- "id": "output_1",
187
- "type": "output",
188
  "position": {"x": 500, "y": 100},
189
- "data": {"label": "Output Node"}
190
  }
191
  ],
192
  "edges": [
193
  {
194
  "id": "edge_1",
195
- "source": "input_1",
196
- "target": "process_1",
197
- "sourceHandle": "output",
198
- "targetHandle": "input"
199
  },
200
  {
201
  "id": "edge_2",
202
- "source": "process_1",
203
- "target": "output_1",
204
- "sourceHandle": "output",
205
- "targetHandle": "input"
206
  }
207
  ]
208
  }
209
- return sample, json.dumps(sample, indent=2, ensure_ascii=False)
210
 
211
- btn_sample = gr.Button("πŸ“₯ Load Sample Workflow", variant="primary")
212
- btn_sample.click(
213
- fn=load_sample_workflow,
214
- outputs=[workflow_builder, code_view]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  # πŸš€ μ‹€ν–‰